summaryrefslogtreecommitdiff
path: root/test/syscall/syscall.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscall/syscall.py')
-rw-r--r--test/syscall/syscall.py849
1 files changed, 849 insertions, 0 deletions
diff --git a/test/syscall/syscall.py b/test/syscall/syscall.py
new file mode 100644
index 00000000000..f97ea8a5749
--- /dev/null
+++ b/test/syscall/syscall.py
@@ -0,0 +1,849 @@
+#!/usr/bin/env python
+#
+# Public Domain 2014-2016 MongoDB, Inc.
+# Public Domain 2008-2014 WiredTiger, Inc.
+#
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or
+# distribute this software, either in source code form or as a compiled
+# binary, for any purpose, commercial or non-commercial, and by any
+# means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors
+# of this software dedicate any and all copyright interest in the
+# software to the public domain. We make this dedication for the benefit
+# of the public at large and to the detriment of our heirs and
+# successors. We intend this dedication to be an overt act of
+# relinquishment in perpetuity of all present and future rights to this
+# software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# syscall.py
+# Command line syscall test runner
+#
+# For each .run file, run the corresponding program and collect strace
+# output, comparing it to the contents of the .run file.
+#
+# run files are first preprocessed, which means the use of #ifdefs, #defines
+# and #includes are allowed, as well as /**/ and // comments.
+# Expressions are evaluated using the Python parser, so that:
+# hex and octal numbers are accepted, constant values can be or-ed.
+#
+from __future__ import print_function
+import argparse, distutils.spawn, fnmatch, os, platform, re, shutil, \
+ subprocess, sys
+
+# A class that represents a context in which predefined constants can be
+# set, and new variables can be assigned.
+class VariableContext(object):
+ def __getitem__(self, key):
+ if key not in dir(self) or key.startswith('__'):
+ raise KeyError(key)
+ return getattr(self, key)
+
+ def __setitem__(self, key, value):
+ setattr(self, key, value)
+
+################################################################
+# Changable parameters
+# We expect these values to evolve as tests are added or modified.
+
+# Generally, system calls must be wrapped in an ASSERT_XX() "macro".
+# Exceptions are calls in this list that return 0 on success, or
+# those that are hardcoded in Runner.call_compare()
+calls_returning_zero = [ 'close', 'ftruncate', 'fdatasync', 'rename' ]
+
+# Encapsulate all the defines we can use in our scripts.
+# When this program is run, we'll find out their actual values on
+# the host system.
+defines_used = [
+ 'HAVE_FTRUNCATE', 'O_ACCMODE', 'O_APPEND', 'O_ASYNC',
+ 'O_CLOEXEC', 'O_CREAT', 'O_EXCL', 'O_EXLOCK', 'O_NOATIME',
+ 'O_NOFOLLOW', 'O_NONBLOCK', 'O_RDONLY', 'O_RDWR', 'O_SHLOCK',
+ 'O_TRUNC', 'O_WRONLY' ]
+
+################################################################
+
+# Patterns that are used to match the .run file and/or the output.
+ident = r'([a-zA-Z_][a-zA-Z0-9_]*)'
+outputpat = re.compile(r'OUTPUT\("([^"]*)"\)')
+argpat = re.compile(r'''((?:[^,"']|"[^"]*"|'[^']*')+)''')
+discardpat = re.compile(r';')
+
+# e.g. fd = open("blah", 0, 0);
+assignpat = re.compile(ident + r'\s*=\s*' + ident + r'(\([^;]*\));')
+
+# e.g. ASSERT_EQ(close(fd), 0);
+assertpat = re.compile(r'ASSERT_([ENLG][QET])\s*\(\s*' + ident + r'\s*(\(.*\))\s*,\s*([a-zA-Z0-9_]+)\);')
+
+# e.g. close(fd); must return 0
+callpat = re.compile(ident + r'(\(.*\));')
+
+# e.g. open("blah", 0x0, 0x0) = 6 0
+# We capture the errno (e.g. "0" or "Err#60"), but don't do anything with it.
+# We don't currently test anything that is errno dependent.
+dtruss_pat = re.compile(ident + r'(\(.*\))\s*=\s*(-*[0-9xA-F]+)\s+([-A-Za-z#0-9]*)')
+# At the top of the dtruss output is a fixed string.
+dtruss_init_pat = re.compile(r'\s*SYSCALL\(args\)\s*=\s*return\s*')
+
+strace_pat = re.compile(ident + r'(\(.*\))\s*=\s(-*[0-9]+)()')
+
+tracepat = re.compile(r'TRACE\("([^"]*)"\)')
+runpat = re.compile(r'RUN\(([^\)]*)\)')
+systempat = re.compile(r'SYSTEM\("([^"]*)"\)')
+# If tracepat matches, set map['trace_syscalls'] to the 0'th group, etc.
+headpatterns = [ [ tracepat, 'trace_syscalls', 0],
+ [ systempat, 'required_system', 0],
+ [ runpat, 'run_args', 0] ]
+
+# To create breakpoints while debugging this script
+def bp():
+ import pdb
+ pdb.set_trace()
+
+def msg(s):
+ print("syscall.py: " + s, file=sys.stderr)
+
+def die(s):
+ msg(s)
+ sys.exit(1)
+
+# If wttop appears as a prefix of pathname, strip it off.
+def simplify_path(wttop, pathname):
+ wttop = os.path.join(wttop, "")
+ if pathname.startswith(wttop):
+ pathname = pathname[len(wttop):]
+ return pathname
+
+def printfile(pathname, abbrev):
+ print("================================================================")
+ print(abbrev + " (" + pathname + "):")
+ with open(pathname, 'r') as f:
+ shutil.copyfileobj(f, sys.stdout)
+ print("================================================================")
+
+# A line from a file: a modified string with the file name and line number
+# associated with it.
+class FileLine(str):
+ filename = None
+ linenum = 0
+ def __new__(cls, value, *args, **kwargs):
+ return super(FileLine, cls).__new__(cls, value)
+
+ def prefix(self):
+ return self.filename + ':' + str(self.linenum) + ': '
+
+ def range_prefix(self, otherline):
+ if self == otherline:
+ othernum = ''
+ elif otherline == None:
+ othernum = '-EOF'
+ else:
+ othernum = '-' + str(otherline.linenum)
+ return self.filename + ':' + str(self.linenum) + othernum + ': '
+
+# Manage reading from a file, tracking line numbers.
+class Reader(object):
+ # 'raw' means we don't ignore any lines
+ # 'is_cpp' means input lines beginning with '#' indicate file/linenumber
+ def __init__(self, wttop, filename, f, raw = True, is_cpp = False):
+ self.wttop = wttop
+ self.orig_filename = filename
+ self.filename = filename
+ self.f = f
+ self.linenum = 1
+ self.raw = raw
+ self.is_cpp = is_cpp
+ if not self.f:
+ die(self.filename + ': cannot open')
+
+ def __enter__(self):
+ if not self.f:
+ return False
+ return self
+
+ def __exit__(self, typ, value, traceback):
+ if self.f:
+ self.f.close()
+ self.f = None
+
+ # Return True if the line is to be ignored.
+ def ignore(self, line):
+ if self.raw:
+ return False
+ return line == ''
+
+ # strip a line of comments
+ def strip_line(self, line):
+ if not line:
+ return None
+ line = line.strip()
+ if self.is_cpp and line.startswith('#'):
+ parts = line.split()
+ if len(parts) < 3 or not parts[1].isdigit():
+ msg('bad cpp input: ' + line)
+ line = ''
+ self.linenum = int(parts[1]) - 1
+ self.filename = parts[2].strip('"')
+ if self.filename == '<stdin>':
+ self.filename = self.orig_filename
+ if '//' in line:
+ if line.startswith('//'):
+ line = ''
+ else:
+ # This isn't exactly right, it would see "; //"
+ # within a string or comment.
+ m = re.match(r'^(.*;|.*\.\.\.)\s*//', line)
+ if m:
+ line = m.groups()[0].strip()
+ return line
+
+ def readline(self):
+ line = self.strip_line(self.f.readline())
+ while line != None and self.ignore(line):
+ self.linenum += 1
+ line = self.strip_line(self.f.readline())
+ if line:
+ line = FileLine(line)
+ line.filename = self.filename
+ line.linenum = self.linenum
+ self.linenum += 1
+ else:
+ line = '' # make this somewhat compatible with file.readline
+ return line
+
+ def close(self):
+ self.f.close()
+
+# Read from a regular file.
+class FileReader(Reader):
+ def __init__(self, wttop, filename, raw = True):
+ return super(FileReader, self).__init__(wttop, filename,
+ open(filename), raw, False)
+
+# Read from the C preprocessor run on a file.
+class PreprocessedReader(Reader):
+ def __init__(self, wttop, filename, predefines, raw = True):
+ sourcedir = os.path.dirname(filename)
+ cmd = ['cc', '-E', '-I' + sourcedir]
+ for name in dir(predefines):
+ if not name.startswith('__'):
+ cmd.append('-D' + name + '=' + str(predefines[name]))
+ cmd.append('-')
+ proc = subprocess.Popen(cmd, stdin=open(filename),
+ stdout=subprocess.PIPE)
+ super(PreprocessedReader, self).__init__(wttop, filename,
+ proc.stdout, raw, True)
+
+# Track options discovered in the 'head' section of the .run file.
+class HeadOpts:
+ def __init__(self):
+ self.run_args = None
+ self.required_system = None
+ self.trace_syscalls = None
+
+# Manage a run of the target program characterized by a .run file,
+# comparing output from the run and reporting differences.
+class Runner:
+ def __init__(self, wttopdir, runfilename, exedir, testexe,
+ strace, args, variables, defines):
+ self.variables = variables
+ self.defines = defines
+ self.wttopdir = wttopdir
+ self.runfilename = runfilename
+ self.testexe = testexe
+ self.exedir = exedir
+ self.strace = strace
+ self.args = args
+ self.headopts = HeadOpts()
+ self.dircreated = False
+ self.strip_syscalls = None
+ outfilename = args.outfilename
+ errfilename = args.errfilename
+ if outfilename == None:
+ self.outfilename = os.path.join(exedir, 'stdout.txt')
+ else:
+ self.outfilename = outfilename
+ if errfilename == None:
+ self.errfilename = os.path.join(exedir, 'stderr.txt')
+ else:
+ self.errfilename = errfilename
+
+ self.runfile = PreprocessedReader(self.wttopdir, runfilename,
+ self.defines, False)
+
+ def init(self, systemtype):
+ # Read up until 'RUN()', setting attributes of self.headopts
+ runline = '?'
+ m = None
+ while runline:
+ runline = self.runfile.readline()
+ m = None
+ for pat,attr,group in headpatterns:
+ m = re.match(pat, runline)
+ if m:
+ setattr(self.headopts, attr, m.groups()[group])
+ break
+ if not m:
+ self.fail(runline, "unknown header option: " + runline)
+ return [ False, False ]
+ if self.headopts.run_args != None: # found RUN()?
+ break
+ if not self.headopts.trace_syscalls:
+ msg("'" + self.runfile.filename + "': needs TRACE(...)")
+ return [ False, False ]
+ runargs = self.headopts.run_args.strip()
+ if len(runargs) > 0:
+ if len(runargs) < 2 or runargs[0] != '"' or runargs[-1] != '"':
+ msg("'" + self.runfile.filename +
+ "': Missing double quotes in RUN arguments")
+ return [ False, False ]
+ runargs = runargs[1:-1]
+ self.runargs = runargs.split()
+ #print("SYSCALLS: " + self.headopts.trace_syscalls
+ if self.headopts.required_system != None and \
+ self.headopts.required_system != systemtype:
+ msg("skipping '" + self.runfile.filename + "': for '" +
+ self.headopts.required_system + "', this system is '"
+ + systemtype + "'")
+ return [ False, True ]
+ return [ True, False ]
+
+ def close(self, forcePreserve):
+ self.runfile.close()
+ if self.exedir and self.dircreated and \
+ not self.args.preserve and not forcePreserve:
+ os.chdir('..')
+ shutil.rmtree(self.exedir)
+
+ def fail(self, line, s):
+ # make it work if line is None or is a plain string.
+ try:
+ prefix = simplify_path(self.wttopdir, line.prefix())
+ except:
+ prefix = 'syscall.py: '
+ print(prefix + s, file=sys.stderr)
+
+ def failrange(self, line, lineto, s):
+ # make it work if line is None or is a plain string.
+ try:
+ prefix = simplify_path(self.wttopdir, line.range_prefix(lineto))
+ except:
+ prefix = 'syscall.py: '
+ print(prefix + s, file=sys.stderr)
+
+ def str_match(self, s1, s2):
+ fuzzyRight = False
+ if len(s1) < 2 or len(s2) < 2:
+ return False
+ if s1[-3:] == '...':
+ fuzzyRight = True
+ s1 = s1[:-3]
+ if s2[-3:] == '...':
+ s2 = s2[:-3]
+ if s1[0] != '"' or s1[-1] != '"' or s2[0] != '"' or s2[-1] != '"':
+ return False
+ s1 = s1[1:-1]
+ s2 = s2[1:-1]
+ # We allow a trailing \0
+ if s1[-2:] == '\\0':
+ s1 = s1[:-2]
+ if s2[-2:] == '\\0':
+ s2 = s2[:-2]
+ if fuzzyRight:
+ return s2.startswith(s2)
+ else:
+ return s1 == s2
+
+ def expr_eval(self, s):
+ return eval(s, {}, self.variables)
+
+ def arg_match(self, a1, a2):
+ a1 = a1.strip()
+ a2 = a2.strip()
+ if a1 == a2:
+ return True
+ if len(a1) == 0 or len(a2) == 0:
+ return False
+ if a1[0] == '"':
+ return self.str_match(a1, a2)
+ #print(' arg_match: <' + a1 + '> <' + a2 + '>')
+ try:
+ a1value = self.expr_eval(a1)
+ except Exception:
+ self.fail(a1, 'unknown expression: ' + a1)
+ return False
+ try:
+ a2value = self.expr_eval(a2)
+ except Exception:
+ self.fail(a2, 'unknown expression: ' + a2)
+ return False
+ return a1value == a2value or int(a1value) == int(a2value)
+
+ def split_args(self, s):
+ if s[0] == '(':
+ s = s[1:]
+ if s[-1] == ')':
+ s = s[:-1]
+ return argpat.split(s)[1::2]
+
+ def args_match(self, args1, args2):
+ #print('args_match: ' + str(s1) + ', ' + str(s2))
+ pos = 0
+ for a1 in args1:
+ a1 = a1.strip()
+ if a1 == '...': # match anything?
+ return True
+ if pos >= len(args2):
+ return False
+ if not self.arg_match(a1, args2[pos]):
+ return False
+ pos += 1
+ if pos < len(args2):
+ return False
+ return True
+
+ # func(args); is shorthand for for ASSERT_EQ(func(args), xxx);
+ # where xxx may be 0 or may be derived from one of the args.
+ def call_compare(self, callname, result, eargs, errline):
+ if callname in calls_returning_zero:
+ return self.compare("EQ", result, "0", errline)
+ elif callname == 'pwrite':
+ return self.compare("EQ", result, eargs[2], errline)
+ else:
+ self.fail(errline, 'call ' + callname +
+ ': not known, use ASSERT_EQ()')
+
+ def compare(self, compareop, left, right, errline):
+ l = self.expr_eval(left)
+ r = self.expr_eval(right)
+ if (compareop == "EQ" and l == r) or \
+ (compareop == "NE" and l != r) or \
+ (compareop == "LT" and l < r) or \
+ (compareop == "LE" and l <= r) or \
+ (compareop == "GT" and l > r) or \
+ (compareop == "GE" and l >= r):
+ return True
+ else:
+ self.fail(errline,
+ 'call returned value: ' + left + ', comparison: (' +
+ left + ' ' + compareop + ' ' + right +
+ ') at line: ' + errline)
+ return False
+
+ def match_report(self, runline, errline, verbose, skiplines, result, desc):
+ if result:
+ if verbose:
+ print('MATCH:')
+ print(' ' + runline.prefix() + runline)
+ print(' ' + errline.prefix() + errline)
+ else:
+ if verbose:
+ if not skiplines:
+ msg('Expecting ' + desc)
+ print(' ' + runline.prefix() + runline +
+ ' does not match:')
+ print(' ' + errline.prefix() + errline)
+ else:
+ print(' (... match) ' + errline.prefix() + errline)
+ return result
+
+ def match(self, runline, errline, verbose, skiplines):
+ m = re.match(outputpat, runline)
+ if m:
+ outwant = m.groups()[0]
+ return self.match_report(runline, errline, verbose, skiplines,
+ errline == outwant, 'output line')
+ if self.args.systype == 'Linux':
+ em = re.match(strace_pat, errline)
+ elif self.args.systype == 'Darwin':
+ em = re.match(dtruss_pat, errline)
+ if not em:
+ self.fail(errline, 'Unknown strace/dtruss output: ' + errline)
+ return False
+ gotcall = em.groups()[0]
+ # filtering syscalls here if needed. If it's not a match,
+ # mark the errline so it is retried.
+ if self.strip_syscalls != None and gotcall not in self.strip_syscalls:
+ errline.skip = True
+ return False
+
+ m = re.match(assignpat, runline)
+ if m:
+ if m.groups()[1] != gotcall:
+ return self.match_report(runline, errline, verbose, skiplines,
+ False, 'syscall to match assignment')
+
+ rargs = self.split_args(m.groups()[2])
+ eargs = self.split_args(em.groups()[1])
+ result = self.args_match(rargs, eargs)
+ if result:
+ self.variables[m.groups()[0]] = em.groups()[2]
+ return self.match_report(runline, errline, verbose, skiplines,
+ result, 'syscall to match assignment')
+
+ # pattern groups using example ASSERT_EQ(close(fd), 0);
+ # 0 : comparison op ("EQ")
+ # 1 : function call name "close"
+ # 2 : function call args "(fd)"
+ # 3 : comparitor "0"
+ m = re.match(assertpat, runline)
+ if m:
+ if m.groups()[1] != gotcall:
+ return self.match_report(runline, errline, verbose, skiplines,
+ False, 'syscall to match ASSERT')
+
+ rargs = self.split_args(m.groups()[2])
+ eargs = self.split_args(em.groups()[1])
+ result = self.args_match(rargs, eargs)
+ if not result:
+ return self.match_report(runline, errline, verbose, skiplines,
+ result, 'syscall to match ASSERT')
+ result = self.compare(m.groups()[0], em.groups()[2],
+ m.groups()[3], errline)
+ return self.match_report(runline, errline, verbose, skiplines,
+ result, 'ASSERT')
+
+ # A call without an enclosing ASSERT is reduced to an ASSERT,
+ # depending on the particular system call.
+ m = re.match(callpat, runline)
+ if m:
+ if m.groups()[0] != gotcall:
+ return self.match_report(runline, errline, verbose, skiplines,
+ False, 'syscall')
+
+ rargs = self.split_args(m.groups()[1])
+ eargs = self.split_args(em.groups()[1])
+ result = self.args_match(rargs, eargs)
+ if not result:
+ return self.match_report(runline, errline, verbose, skiplines,
+ result, 'syscall')
+ result = self.call_compare(m.groups()[0], em.groups()[2],
+ eargs, errline)
+ return self.match_report(runline, errline, verbose, skiplines,
+ result, 'syscall')
+
+ self.fail(runline, 'unrecognized pattern in runfile:' + runline)
+ return False
+
+ def match_lines(self):
+ outfile = FileReader(self.wttopdir, self.outfilename, True)
+ errfile = FileReader(self.wttopdir, self.errfilename, True)
+
+ if outfile.readline():
+ self.fail(None, 'output file has content, expected to be empty')
+ return False
+
+ with outfile, errfile:
+ runlines = self.order_runfile(self.runfile)
+ errline = errfile.readline()
+ if re.match(dtruss_init_pat, errline):
+ errline = errfile.readline()
+ skiplines = False
+ for runline in runlines:
+ if runline == '...':
+ skiplines = True
+ if self.args.verbose:
+ print('Fuzzy matching:')
+ print(' ' + runline.prefix() + runline)
+ continue
+ first_errline = errline
+ while errline and not self.match(runline, errline,
+ self.args.verbose, skiplines):
+ if skiplines or hasattr(errline, 'skip'):
+ errline = errfile.readline()
+ else:
+ self.fail(runline, "expecting " + runline)
+ self.failrange(first_errline, errline, "does not match")
+ return False
+ if not errline:
+ self.fail(runline, "failed to match line: " + runline)
+ self.failrange(first_errline, errline, "does not match")
+ return False
+ errline = errfile.readline()
+ if re.match(dtruss_init_pat, errline):
+ errline = errfile.readline()
+ skiplines = False
+ if errline and not skiplines:
+ self.fail(errline, "extra lines seen starting at " + errline)
+ return False
+ return True
+
+ def order_runfile(self, f):
+ # In OS X, dtruss is implemented using dtrace's apparently buffered
+ # printf writes to stdout, but that is all redirected to stderr.
+ # Because of that, the test program's writes to stderr do not
+ # interleave with dtruss output as it does with Linux's strace
+ # (which writes directly to stderr). On OS X, we get the program's
+ # output first, we compensate for this by moving all the
+ # OUTPUT statements in the runfile to match first. This simple
+ # approach will break if there is more data generated by OUTPUT
+ # statements than a stdio buffer's size.
+ matchout = (self.args.systype == 'Darwin')
+ out = []
+ nonout = []
+ s = f.readline()
+ while s:
+ if matchout and re.match(outputpat, s):
+ out.append(s)
+ elif not re.match(discardpat, s):
+ nonout.append(s)
+ s = f.readline()
+ out.extend(nonout)
+ return out
+
+ def run(self):
+ if not self.exedir:
+ self.fail(None, "Execution directory not set")
+ return False
+ if not os.path.isfile(self.testexe):
+ msg("'" + self.testexe + "': no such file")
+ return False
+
+ shutil.rmtree(self.exedir, ignore_errors=True)
+ os.mkdir(self.exedir)
+ self.dircreated = True
+ os.chdir(self.exedir)
+
+ callargs = list(self.strace)
+ trace_syscalls = self.headopts.trace_syscalls
+ if self.args.systype == 'Linux':
+ callargs.extend(['-e', 'trace=' + trace_syscalls ])
+ elif self.args.systype == 'Darwin':
+ # dtrace has no option to limit the syscalls to be traced,
+ # so we'll filter the output.
+ self.strip_syscalls = self.headopts.trace_syscalls.split(',')
+ callargs.append(self.testexe)
+ callargs.extend(self.runargs)
+
+ outfile = open(self.outfilename, 'w')
+ errfile = open(self.errfilename, 'w')
+ if self.args.verbose:
+ print('RUNNING: ' + str(callargs))
+ subret = subprocess.call(callargs, stdout=outfile, stderr=errfile)
+ outfile.close()
+ errfile.close()
+ if subret != 0:
+ msg("'" + self.testexe + "': exit value " + str(subret))
+ printfile(self.outfilename, "output")
+ printfile(self.errfilename, "error")
+ return False
+ return True
+
+# Run the syscall program.
+class SyscallCommand:
+ def __init__(self, disttop, builddir):
+ self.disttop = disttop
+ self.builddir = builddir
+
+ def parse_args(self, argv):
+ srcdir = os.path.join(self.disttop, 'test', 'syscall')
+ self.exetopdir = os.path.join(self.builddir, 'test', 'syscall')
+ self.incdir1 = os.path.join(self.disttop, 'src', 'include')
+ self.incdir2 = self.builddir
+
+ ap = argparse.ArgumentParser('Syscall test runner')
+ ap.add_argument('--systype',
+ help='override system type (Linux/Windows/Darwin)')
+ ap.add_argument('--errfile', dest='errfilename',
+ help='do not run the program, use this file as stderr')
+ ap.add_argument('--outfile', dest='outfilename',
+ help='do not run the program, use this file as stdout')
+ ap.add_argument('--preserve', action="store_true",
+ help='keep the WT_TEST.* directories')
+ ap.add_argument('--verbose', action="store_true",
+ help='add some verbose information')
+ ap.add_argument('tests', nargs='*',
+ help='the tests to run (defaults to all)')
+ args = ap.parse_args()
+
+ if not args.systype:
+ args.systype = platform.system() # Linux, Windows, Darwin
+
+ self.dorun = True
+ if args.errfilename or args.outfilename:
+ if len(args.tests) != 1:
+ msg("one test is required when --errfile or --outfile" +
+ " is specified")
+ return False
+ if not args.outfilename:
+ args.outfilename = os.devnull
+ if not args.errfilename:
+ args.errfilename = os.devnull
+ self.dorun = False
+
+ # for now, we permit Linux and Darwin
+ straceexe = None
+ if args.systype == 'Linux':
+ strace = [ 'strace' ]
+ straceexe = 'strace'
+ elif args.systype == 'Darwin':
+ strace = [ 'sudo', 'dtruss' ]
+ straceexe = 'dtruss'
+ else:
+ msg("systype '" + args.systype + "' unsupported")
+ return False
+ if not distutils.spawn.find_executable(straceexe):
+ msg("strace: does not exist")
+ return False
+ self.args = args
+ self.strace = strace
+ return True
+
+ def runone(self, runfilename, exedir, testexe, args):
+ result = True
+ runner = Runner(self.disttop, runfilename, exedir, testexe,
+ self.strace, args, self.variables, self.defines)
+ okay, skip = runner.init(args.systype)
+ if not okay:
+ if not skip:
+ result = False
+ else:
+ if testexe:
+ print('running ' + testexe)
+ if not runner.run():
+ result = False
+ if result:
+ print('comparing:')
+ print(' ' + simplify_path(self.disttop, runfilename))
+ print(' ' + simplify_path(self.disttop, runner.errfilename))
+ result = runner.match_lines()
+ if not result and args.verbose:
+ printfile(runfilename, "runfile")
+ printfile(runner.errfilename, "trace output")
+ runner.close(not result)
+ if not result:
+ print('************************ FAILED ************************')
+ print(' see results in ' + exedir)
+ print('')
+ return result
+
+ # Create a C program to get values for all defines we need.
+ # The output of the program is Python code that we'll execute
+ # directly to set the values.
+ def build_system_defines(self):
+ # variables is a symbol table that is used to
+ # evaluate expressions both in the .run file and
+ # in the output file. This is needed for strace,
+ # which shows system call flags in symbolic form.
+ self.variables = VariableContext()
+ # defines is a symbol table that is used to
+ # create preprocessor defines, effectively evaluating
+ # all flag defines in the .run file.
+ self.defines = VariableContext()
+ program = \
+ '#include <stdio.h>\n' + \
+ '#include <fcntl.h>\n' + \
+ '#include <wt_internal.h>\n' + \
+ 'int main() {\n'
+ for define in defines_used:
+ program += '#ifdef ' + define + '\n'
+ # output is Python that sets attributes of 'o'.
+ program += ' printf("o.' + define + '=%d\\n", ' + \
+ define + ');\n'
+ program += '#endif\n'
+ program += \
+ ' return(0);\n' + \
+ '}\n'
+ probe_c = os.path.join(self.exetopdir, "syscall_probe.c")
+ probe_exe = os.path.join(self.exetopdir, "syscall_probe")
+ with open(probe_c, "w") as f:
+ f.write(program)
+ ccargs = ['cc', '-o', probe_exe]
+ ccargs.append('-I' + self.incdir1)
+ ccargs.append('-I' + self.incdir2)
+ if self.args.systype == 'Linux':
+ ccargs.append('-D_GNU_SOURCE')
+ ccargs.append(probe_c)
+ subret = subprocess.call(ccargs)
+ if subret != 0:
+ msg("probe compilation returned " + str(subret))
+ return False
+ proc = subprocess.Popen([probe_exe], stdout=subprocess.PIPE)
+ out, err = proc.communicate()
+ subret = proc.returncode
+ if subret != 0 or err:
+ msg("probe run returned " + str(subret) + ", error=" + str(err))
+ return False
+ if self.args.verbose:
+ print('probe output:\n' + out)
+ o = self.defines # The 'o' object will be modified.
+ exec(out) # Run the produced Python.
+ o = self.variables # Set these in variables too, so strace
+ exec(out) # symbolic output is evaluated.
+ if not self.args.preserve:
+ os.remove(probe_c)
+ os.remove(probe_exe)
+ return True
+
+ def execute(self):
+ args = self.args
+ result = True
+ if not self.build_system_defines():
+ die('cannot build system defines')
+ if not self.dorun:
+ for testname in args.tests:
+ result = self.runone(testname, None, None, args) and result
+ else:
+ if len(args.tests) > 0:
+ tests = []
+ for arg in args.tests:
+ abspath = os.path.abspath(arg)
+ tests.append([os.path.dirname(abspath), [],
+ [os.path.basename(abspath)]])
+ else:
+ tests = os.walk(syscalldir)
+ os.chdir(self.exetopdir)
+ for path, subdirs, files in tests:
+ testnum = -1 if len(files) <= 1 else 0
+ for name in files:
+ if fnmatch.fnmatch(name, '*.run'):
+ testname = os.path.basename(os.path.normpath(path))
+ runfilename = os.path.join(path, name)
+ testexe = os.path.join(self.exetopdir,
+ 'test_' + testname)
+ exedir = os.path.join(self.exetopdir,
+ 'WT_TEST.' + testname)
+ # If there are multiple tests in this directory,
+ # give each one its own execution dir.
+ if testnum >= 0:
+ exedir += '.' + str(testnum)
+ testnum += 1
+ result = self.runone(runfilename, exedir,
+ testexe, args) and result
+ return result
+
+# Set paths, determining the top of the build.
+syscalldir = sys.path[0]
+wt_disttop = os.path.dirname(os.path.dirname(syscalldir))
+
+# Note: this code is borrowed from test/suite/run.py
+# Check for a local build that contains the wt utility. First check in
+# current working directory, then in build_posix and finally in the disttop
+# directory. This isn't ideal - if a user has multiple builds in a tree we
+# could pick the wrong one.
+if os.path.isfile(os.path.join(os.getcwd(), 'wt')):
+ wt_builddir = os.getcwd()
+elif os.path.isfile(os.path.join(wt_disttop, 'wt')):
+ wt_builddir = wt_disttop
+elif os.path.isfile(os.path.join(wt_disttop, 'build_posix', 'wt')):
+ wt_builddir = os.path.join(wt_disttop, 'build_posix')
+elif os.path.isfile(os.path.join(wt_disttop, 'wt.exe')):
+ wt_builddir = wt_disttop
+else:
+ die('unable to find useable WiredTiger build')
+
+cmd = SyscallCommand(wt_disttop, wt_builddir)
+if not cmd.parse_args(sys.argv):
+ die('bad usage')
+if not cmd.execute():
+ sys.exit(1)
+sys.exit(0)