diff options
Diffstat (limited to 'src/third_party/wiredtiger/test/syscall/syscall.py')
-rw-r--r-- | src/third_party/wiredtiger/test/syscall/syscall.py | 857 |
1 files changed, 857 insertions, 0 deletions
diff --git a/src/third_party/wiredtiger/test/syscall/syscall.py b/src/third_party/wiredtiger/test/syscall/syscall.py new file mode 100644 index 00000000000..59c2f347146 --- /dev/null +++ b/src/third_party/wiredtiger/test/syscall/syscall.py @@ -0,0 +1,857 @@ +#!/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] ] + +pwrite_in = r'pwrite64' +pwrite_out = r'pwrite' + +# 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' or callname == 'pwrite64': + return self.compare("EQ", + re.sub(pwrite_in, pwrite_out, result), + re.sub(pwrite_in, pwrite_out, 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 = re.sub(pwrite_in, pwrite_out, 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() + errline = re.sub(pwrite_in, pwrite_out, errline) + if re.match(dtruss_init_pat, errline): + errline = errfile.readline() + skiplines = False + for runline in runlines: + runline = re.sub(pwrite_in, pwrite_out, runline) + 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 = re.sub(pwrite_in, pwrite_out, + 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) |