1#!/usr/bin/env python
2#
3# Public Domain 2014-2018 MongoDB, Inc.
4# Public Domain 2008-2014 WiredTiger, Inc.
5#
6# This is free and unencumbered software released into the public domain.
7#
8# Anyone is free to copy, modify, publish, use, compile, sell, or
9# distribute this software, either in source code form or as a compiled
10# binary, for any purpose, commercial or non-commercial, and by any
11# means.
12#
13# In jurisdictions that recognize copyright laws, the author or authors
14# of this software dedicate any and all copyright interest in the
15# software to the public domain. We make this dedication for the benefit
16# of the public at large and to the detriment of our heirs and
17# successors. We intend this dedication to be an overt act of
18# relinquishment in perpetuity of all present and future rights to this
19# software under copyright law.
20#
21# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
25# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
26# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27# OTHER DEALINGS IN THE SOFTWARE.
28
29import os, re, subprocess, sys
30from run import wt_builddir
31from wttest import WiredTigerTestCase
32
33# suite_subprocess.py
34#    Run a subprocess within the test suite
35# Used as a 'mixin' class along with a WiredTigerTestCase class
36class suite_subprocess:
37    subproc = None
38
39    def has_error_in_file(self, filename):
40        """
41        Return whether the file contains 'ERROR'.
42        WT utilities issue a 'WT_ERROR' output string upon error.
43        """
44        with open(filename, 'r') as f:
45            for line in f:
46                if 'ERROR' in line:
47                    return True
48        return False
49
50    def check_no_error_in_file(self, filename, match='ERROR'):
51        """
52        Raise an error and show output context if the file contains 'ERROR'.
53        WT utilities issue a 'WT_ERROR' output string upon error.
54        """
55        lines = []
56        hasError = False
57        hasPrevious = False  # do we need to prefix an ellipsis?
58        hasNext = False  # do we need to suffix an ellipsis?
59        with open(filename, 'r') as f:
60            for line in f:
61                lines.append(line)
62                hasError = hasError or match in line
63                if hasError:
64                    if len(lines) > 10:
65                        hasNext = True
66                        break
67                else:
68                    if len(lines) > 5:
69                        lines.pop(0)
70                        hasPrevious = True
71        if hasError:
72            print '**************** ' + match + ' in output file: ' + filename + ' ****************'
73            if hasPrevious:
74                print '...'
75            for line in lines:
76                print line,
77            if hasNext:
78                print '...'
79            print '********************************'
80            self.fail('ERROR found in output file: ' + filename)
81
82    # If the string is of the form '/.../', then return just the embedded
83    # pattern, otherwise, return None
84    def convert_to_pattern(self, s):
85        if len(s) >= 2 and s[0] == '/' and s[-1] == '/':
86            return s[1:-1]
87        else:
88            return None
89
90    def check_file_content(self, filename, expect):
91        with open(filename, 'r') as f:
92            got = f.read(len(expect) + 100)
93            self.assertEqual(got, expect, filename + ': does not contain expected:\n\'' + expect + '\', but contains:\n\'' + got + '\'.')
94
95    def check_file_contains_one_of(self, filename, expectlist):
96        """
97        Check that the file contains the expected string in the first 100K bytes
98        """
99        maxbytes = 1024*100
100        with open(filename, 'r') as f:
101            got = f.read(maxbytes)
102            found = False
103            for expect in expectlist:
104                pat = self.convert_to_pattern(expect)
105                if pat == None:
106                    if expect in got:
107                        found = True
108                        break
109                else:
110                    if re.search(pat, got):
111                        found = True
112                        break
113            if not found:
114                if len(expectlist) == 1:
115                    expect = '\'' + expectlist[0] + '\''
116                else:
117                    expect = str(expectlist)
118                gotstr = '\'' + \
119                    (got if len(got) < 1000 else (got[0:1000] + '...')) + '\''
120                if len(got) >= maxbytes:
121                    self.fail(filename + ': does not contain expected ' + expect + ', or output is too large, got ' + gotstr)
122                else:
123                    self.fail(filename + ': does not contain expected ' + expect + ', got ' + gotstr)
124
125    def check_file_contains(self, filename, expect):
126        self.check_file_contains_one_of(filename, [expect])
127
128    def check_empty_file(self, filename):
129        """
130        Raise an error if the file is not empty
131        """
132        filesize = os.path.getsize(filename)
133        if filesize > 0:
134            with open(filename, 'r') as f:
135                contents = f.read(1000)
136                print 'ERROR: ' + filename + ' expected to be empty, but contains:\n'
137                print contents + '...\n'
138        self.assertEqual(filesize, 0, filename + ': expected to be empty')
139
140    def check_non_empty_file(self, filename):
141        """
142        Raise an error if the file is empty
143        """
144        filesize = os.path.getsize(filename)
145        if filesize == 0:
146            print 'ERROR: ' + filename + ' should not be empty (this command expected error output)'
147        self.assertNotEqual(filesize, 0, filename + ': expected to not be empty')
148
149    def verbose_env(self, envvar):
150        return envvar + '=' + str(os.environ.get(envvar)) + '\n'
151
152    def show_outputs(self, procargs, message, filenames):
153        out = 'ERROR: wt command ' + message + ': ' + str(procargs) + '\n' + \
154              self.verbose_env('PATH') + \
155              self.verbose_env('LD_LIBRARY_PATH') + \
156              self.verbose_env('DYLD_LIBRARY_PATH') + \
157              self.verbose_env('PYTHONPATH') + \
158              'output files follow:'
159        WiredTigerTestCase.prout(out)
160        for filename in filenames:
161            maxbytes = 1024*100
162            with open(filename, 'r') as f:
163                contents = f.read(maxbytes)
164                if len(contents) > 0:
165                    if len(contents) >= maxbytes:
166                        contents += '...\n'
167                    sepline = '*' * 50 + '\n'
168                    out = sepline + filename + '\n' + sepline + contents
169                    WiredTigerTestCase.prout(out)
170
171    # Run the wt utility.
172    def runWt(self, args, infilename=None,
173        outfilename=None, errfilename=None, closeconn=True,
174        reopensession=True, failure=False):
175
176        # Close the connection to guarantee everything is flushed, and that
177        # we can open it from another process.
178        if closeconn:
179            self.close_conn()
180
181        wtoutname = outfilename or "wt.out"
182        wterrname = errfilename or "wt.err"
183        with open(wterrname, "w") as wterr:
184            with open(wtoutname, "w") as wtout:
185                # Prefer running the actual 'wt' executable rather than the
186                # 'wt' script created by libtool. On OS/X with System Integrity
187                # Protection enabled, running a shell script strips
188                # environment variables needed to run 'wt'.
189                if sys.platform == "darwin":
190                    wtexe = os.path.join(wt_builddir, ".libs", "wt")
191                else:
192                    wtexe = os.path.join(wt_builddir, "wt")
193                procargs = [ wtexe ]
194                if self._gdbSubprocess:
195                    procargs = [ "gdb", "--args" ] + procargs
196                elif self._lldbSubprocess:
197                    procargs = [ "lldb", "--" ] + procargs
198                procargs.extend(args)
199                if self._gdbSubprocess:
200                    infilepart = ""
201                    if infilename != None:
202                        infilepart = "<" + infilename + " "
203                    print str(procargs)
204                    print "*********************************************"
205                    print "**** Run 'wt' via: run " + \
206                        " ".join(procargs[3:]) + infilepart + \
207                        ">" + wtoutname + " 2>" + wterrname
208                    print "*********************************************"
209                    returncode = subprocess.call(procargs)
210                elif self._lldbSubprocess:
211                    infilepart = ""
212                    if infilename != None:
213                        infilepart = "<" + infilename + " "
214                    print str(procargs)
215                    print "*********************************************"
216                    print "**** Run 'wt' via: run " + \
217                        " ".join(procargs[3:]) + infilepart + \
218                        ">" + wtoutname + " 2>" + wterrname
219                    print "*********************************************"
220                    returncode = subprocess.call(procargs)
221                elif infilename:
222                    with open(infilename, "r") as wtin:
223                        returncode = subprocess.call(
224                            procargs, stdin=wtin, stdout=wtout, stderr=wterr)
225                else:
226                    returncode = subprocess.call(
227                        procargs, stdout=wtout, stderr=wterr)
228        if failure:
229            if returncode == 0:
230                self.show_outputs(procargs, "expected failure, got success",
231                                  [wtoutname, wterrname])
232            self.assertNotEqual(returncode, 0,
233                'expected failure: "' + \
234                str(procargs) + '": exited ' + str(returncode))
235        else:
236            if returncode != 0:
237                self.show_outputs(procargs, "expected success, got failure",
238                                  [wtoutname, wterrname])
239            self.assertEqual(returncode, 0,
240                'expected success: "' + \
241                str(procargs) + '": exited ' + str(returncode))
242        if errfilename == None:
243            self.check_empty_file(wterrname)
244        if outfilename == None:
245            self.check_empty_file(wtoutname)
246
247        # Reestablish the connection if needed
248        if reopensession and closeconn:
249            self.open_conn()
250