##// END OF EJS Templates
run-tests: show PID if running in parallel mode with -v....
Greg Ward -
r8671:a434c94b default
parent child Browse files
Show More
@@ -1,709 +1,714 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
8 # GNU General Public License version 2, incorporated herein by reference.
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import optparse
12 import optparse
13 import os
13 import os
14 import subprocess
14 import subprocess
15 import shutil
15 import shutil
16 import signal
16 import signal
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19 import time
19 import time
20
20
21 closefds = os.name == 'posix'
21 closefds = os.name == 'posix'
22 def Popen4(cmd, bufsize=-1):
22 def Popen4(cmd, bufsize=-1):
23 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
23 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
24 close_fds=closefds,
24 close_fds=closefds,
25 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
25 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
26 stderr=subprocess.STDOUT)
26 stderr=subprocess.STDOUT)
27 p.fromchild = p.stdout
27 p.fromchild = p.stdout
28 p.tochild = p.stdin
28 p.tochild = p.stdin
29 p.childerr = p.stderr
29 p.childerr = p.stderr
30 return p
30 return p
31
31
32 # reserved exit code to skip test (used by hghave)
32 # reserved exit code to skip test (used by hghave)
33 SKIPPED_STATUS = 80
33 SKIPPED_STATUS = 80
34 SKIPPED_PREFIX = 'skipped: '
34 SKIPPED_PREFIX = 'skipped: '
35 FAILED_PREFIX = 'hghave check failed: '
35 FAILED_PREFIX = 'hghave check failed: '
36 PYTHON = sys.executable
36 PYTHON = sys.executable
37 hgpkg = None
37 hgpkg = None
38
38
39 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
39 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
40
40
41 defaults = {
41 defaults = {
42 'jobs': ('HGTEST_JOBS', 1),
42 'jobs': ('HGTEST_JOBS', 1),
43 'timeout': ('HGTEST_TIMEOUT', 180),
43 'timeout': ('HGTEST_TIMEOUT', 180),
44 'port': ('HGTEST_PORT', 20059),
44 'port': ('HGTEST_PORT', 20059),
45 }
45 }
46
46
47 def parseargs():
47 def parseargs():
48 parser = optparse.OptionParser("%prog [options] [tests]")
48 parser = optparse.OptionParser("%prog [options] [tests]")
49 parser.add_option("-C", "--annotate", action="store_true",
49 parser.add_option("-C", "--annotate", action="store_true",
50 help="output files annotated with coverage")
50 help="output files annotated with coverage")
51 parser.add_option("--child", type="int",
51 parser.add_option("--child", type="int",
52 help="run as child process, summary to given fd")
52 help="run as child process, summary to given fd")
53 parser.add_option("-c", "--cover", action="store_true",
53 parser.add_option("-c", "--cover", action="store_true",
54 help="print a test coverage report")
54 help="print a test coverage report")
55 parser.add_option("-f", "--first", action="store_true",
55 parser.add_option("-f", "--first", action="store_true",
56 help="exit on the first test failure")
56 help="exit on the first test failure")
57 parser.add_option("-i", "--interactive", action="store_true",
57 parser.add_option("-i", "--interactive", action="store_true",
58 help="prompt to accept changed output")
58 help="prompt to accept changed output")
59 parser.add_option("-j", "--jobs", type="int",
59 parser.add_option("-j", "--jobs", type="int",
60 help="number of jobs to run in parallel"
60 help="number of jobs to run in parallel"
61 " (default: $%s or %d)" % defaults['jobs'])
61 " (default: $%s or %d)" % defaults['jobs'])
62 parser.add_option("--keep-tmpdir", action="store_true",
62 parser.add_option("--keep-tmpdir", action="store_true",
63 help="keep temporary directory after running tests"
63 help="keep temporary directory after running tests"
64 " (best used with --tmpdir)")
64 " (best used with --tmpdir)")
65 parser.add_option("-R", "--restart", action="store_true",
65 parser.add_option("-R", "--restart", action="store_true",
66 help="restart at last error")
66 help="restart at last error")
67 parser.add_option("-p", "--port", type="int",
67 parser.add_option("-p", "--port", type="int",
68 help="port on which servers should listen"
68 help="port on which servers should listen"
69 " (default: $%s or %d)" % defaults['port'])
69 " (default: $%s or %d)" % defaults['port'])
70 parser.add_option("-r", "--retest", action="store_true",
70 parser.add_option("-r", "--retest", action="store_true",
71 help="retest failed tests")
71 help="retest failed tests")
72 parser.add_option("-s", "--cover_stdlib", action="store_true",
72 parser.add_option("-s", "--cover_stdlib", action="store_true",
73 help="print a test coverage report inc. standard libraries")
73 help="print a test coverage report inc. standard libraries")
74 parser.add_option("-t", "--timeout", type="int",
74 parser.add_option("-t", "--timeout", type="int",
75 help="kill errant tests after TIMEOUT seconds"
75 help="kill errant tests after TIMEOUT seconds"
76 " (default: $%s or %d)" % defaults['timeout'])
76 " (default: $%s or %d)" % defaults['timeout'])
77 parser.add_option("--tmpdir", type="string",
77 parser.add_option("--tmpdir", type="string",
78 help="run tests in the given temporary directory")
78 help="run tests in the given temporary directory")
79 parser.add_option("-v", "--verbose", action="store_true",
79 parser.add_option("-v", "--verbose", action="store_true",
80 help="output verbose messages")
80 help="output verbose messages")
81 parser.add_option("-n", "--nodiff", action="store_true",
81 parser.add_option("-n", "--nodiff", action="store_true",
82 help="skip showing test changes")
82 help="skip showing test changes")
83 parser.add_option("--with-hg", type="string",
83 parser.add_option("--with-hg", type="string",
84 help="test existing install at given location")
84 help="test existing install at given location")
85 parser.add_option("--pure", action="store_true",
85 parser.add_option("--pure", action="store_true",
86 help="use pure Python code instead of C extensions")
86 help="use pure Python code instead of C extensions")
87
87
88 for option, default in defaults.items():
88 for option, default in defaults.items():
89 defaults[option] = int(os.environ.get(*default))
89 defaults[option] = int(os.environ.get(*default))
90 parser.set_defaults(**defaults)
90 parser.set_defaults(**defaults)
91 (options, args) = parser.parse_args()
91 (options, args) = parser.parse_args()
92
92
93 global vlog
93 global vlog
94 options.anycoverage = (options.cover or
94 options.anycoverage = (options.cover or
95 options.cover_stdlib or
95 options.cover_stdlib or
96 options.annotate)
96 options.annotate)
97
97
98 if options.verbose:
98 if options.verbose:
99 if options.jobs > 1 or options.child is not None:
100 pid = "[%d]" % os.getpid()
101 else:
102 pid = None
99 def vlog(*msg):
103 def vlog(*msg):
104 if pid:
105 print pid,
100 for m in msg:
106 for m in msg:
101 print m,
107 print m,
102 print
108 print
103 else:
109 else:
104 vlog = lambda *msg: None
110 vlog = lambda *msg: None
105
111
106 if options.jobs < 1:
112 if options.jobs < 1:
107 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
113 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
108 sys.exit(1)
114 sys.exit(1)
109 if options.interactive and options.jobs > 1:
115 if options.interactive and options.jobs > 1:
110 print '(--interactive overrides --jobs)'
116 print '(--interactive overrides --jobs)'
111 options.jobs = 1
117 options.jobs = 1
112
118
113 return (options, args)
119 return (options, args)
114
120
115 def rename(src, dst):
121 def rename(src, dst):
116 """Like os.rename(), trade atomicity and opened files friendliness
122 """Like os.rename(), trade atomicity and opened files friendliness
117 for existing destination support.
123 for existing destination support.
118 """
124 """
119 shutil.copy(src, dst)
125 shutil.copy(src, dst)
120 os.remove(src)
126 os.remove(src)
121
127
122 def splitnewlines(text):
128 def splitnewlines(text):
123 '''like str.splitlines, but only split on newlines.
129 '''like str.splitlines, but only split on newlines.
124 keep line endings.'''
130 keep line endings.'''
125 i = 0
131 i = 0
126 lines = []
132 lines = []
127 while True:
133 while True:
128 n = text.find('\n', i)
134 n = text.find('\n', i)
129 if n == -1:
135 if n == -1:
130 last = text[i:]
136 last = text[i:]
131 if last:
137 if last:
132 lines.append(last)
138 lines.append(last)
133 return lines
139 return lines
134 lines.append(text[i:n+1])
140 lines.append(text[i:n+1])
135 i = n + 1
141 i = n + 1
136
142
137 def parsehghaveoutput(lines):
143 def parsehghaveoutput(lines):
138 '''Parse hghave log lines.
144 '''Parse hghave log lines.
139 Return tuple of lists (missing, failed):
145 Return tuple of lists (missing, failed):
140 * the missing/unknown features
146 * the missing/unknown features
141 * the features for which existence check failed'''
147 * the features for which existence check failed'''
142 missing = []
148 missing = []
143 failed = []
149 failed = []
144 for line in lines:
150 for line in lines:
145 if line.startswith(SKIPPED_PREFIX):
151 if line.startswith(SKIPPED_PREFIX):
146 line = line.splitlines()[0]
152 line = line.splitlines()[0]
147 missing.append(line[len(SKIPPED_PREFIX):])
153 missing.append(line[len(SKIPPED_PREFIX):])
148 elif line.startswith(FAILED_PREFIX):
154 elif line.startswith(FAILED_PREFIX):
149 line = line.splitlines()[0]
155 line = line.splitlines()[0]
150 failed.append(line[len(FAILED_PREFIX):])
156 failed.append(line[len(FAILED_PREFIX):])
151
157
152 return missing, failed
158 return missing, failed
153
159
154 def showdiff(expected, output):
160 def showdiff(expected, output):
155 for line in difflib.unified_diff(expected, output,
161 for line in difflib.unified_diff(expected, output,
156 "Expected output", "Test output"):
162 "Expected output", "Test output"):
157 sys.stdout.write(line)
163 sys.stdout.write(line)
158
164
159 def findprogram(program):
165 def findprogram(program):
160 """Search PATH for a executable program"""
166 """Search PATH for a executable program"""
161 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
167 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
162 name = os.path.join(p, program)
168 name = os.path.join(p, program)
163 if os.access(name, os.X_OK):
169 if os.access(name, os.X_OK):
164 return name
170 return name
165 return None
171 return None
166
172
167 def checktools():
173 def checktools():
168 # Before we go any further, check for pre-requisite tools
174 # Before we go any further, check for pre-requisite tools
169 # stuff from coreutils (cat, rm, etc) are not tested
175 # stuff from coreutils (cat, rm, etc) are not tested
170 for p in requiredtools:
176 for p in requiredtools:
171 if os.name == 'nt':
177 if os.name == 'nt':
172 p += '.exe'
178 p += '.exe'
173 found = findprogram(p)
179 found = findprogram(p)
174 if found:
180 if found:
175 vlog("# Found prerequisite", p, "at", found)
181 vlog("# Found prerequisite", p, "at", found)
176 else:
182 else:
177 print "WARNING: Did not find prerequisite tool: "+p
183 print "WARNING: Did not find prerequisite tool: "+p
178
184
179 def cleanup(options):
185 def cleanup(options):
180 if not options.keep_tmpdir:
186 if not options.keep_tmpdir:
181 if options.verbose:
187 vlog("# Cleaning up HGTMP", HGTMP)
182 print "# Cleaning up HGTMP", HGTMP
183 shutil.rmtree(HGTMP, True)
188 shutil.rmtree(HGTMP, True)
184
189
185 def usecorrectpython():
190 def usecorrectpython():
186 # some tests run python interpreter. they must use same
191 # some tests run python interpreter. they must use same
187 # interpreter we use or bad things will happen.
192 # interpreter we use or bad things will happen.
188 exedir, exename = os.path.split(sys.executable)
193 exedir, exename = os.path.split(sys.executable)
189 if exename == 'python':
194 if exename == 'python':
190 path = findprogram('python')
195 path = findprogram('python')
191 if os.path.dirname(path) == exedir:
196 if os.path.dirname(path) == exedir:
192 return
197 return
193 vlog('# Making python executable in test path use correct Python')
198 vlog('# Making python executable in test path use correct Python')
194 mypython = os.path.join(BINDIR, 'python')
199 mypython = os.path.join(BINDIR, 'python')
195 try:
200 try:
196 os.symlink(sys.executable, mypython)
201 os.symlink(sys.executable, mypython)
197 except AttributeError:
202 except AttributeError:
198 # windows fallback
203 # windows fallback
199 shutil.copyfile(sys.executable, mypython)
204 shutil.copyfile(sys.executable, mypython)
200 shutil.copymode(sys.executable, mypython)
205 shutil.copymode(sys.executable, mypython)
201
206
202 def installhg(options):
207 def installhg(options):
203 global PYTHON
208 global PYTHON
204 vlog("# Performing temporary installation of HG")
209 vlog("# Performing temporary installation of HG")
205 installerrs = os.path.join("tests", "install.err")
210 installerrs = os.path.join("tests", "install.err")
206 pure = options.pure and "--pure" or ""
211 pure = options.pure and "--pure" or ""
207
212
208 # Run installer in hg root
213 # Run installer in hg root
209 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
214 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
210 cmd = ('%s setup.py %s clean --all'
215 cmd = ('%s setup.py %s clean --all'
211 ' install --force --prefix="%s" --install-lib="%s"'
216 ' install --force --prefix="%s" --install-lib="%s"'
212 ' --install-scripts="%s" >%s 2>&1'
217 ' --install-scripts="%s" >%s 2>&1'
213 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
218 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
214 vlog("# Running", cmd)
219 vlog("# Running", cmd)
215 if os.system(cmd) == 0:
220 if os.system(cmd) == 0:
216 if not options.verbose:
221 if not options.verbose:
217 os.remove(installerrs)
222 os.remove(installerrs)
218 else:
223 else:
219 f = open(installerrs)
224 f = open(installerrs)
220 for line in f:
225 for line in f:
221 print line,
226 print line,
222 f.close()
227 f.close()
223 sys.exit(1)
228 sys.exit(1)
224 os.chdir(TESTDIR)
229 os.chdir(TESTDIR)
225
230
226 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
231 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
227
232
228 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
233 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
229 pythonpath = os.environ.get("PYTHONPATH")
234 pythonpath = os.environ.get("PYTHONPATH")
230 if pythonpath:
235 if pythonpath:
231 pythonpath = pydir + os.pathsep + pythonpath
236 pythonpath = pydir + os.pathsep + pythonpath
232 else:
237 else:
233 pythonpath = pydir
238 pythonpath = pydir
234 os.environ["PYTHONPATH"] = pythonpath
239 os.environ["PYTHONPATH"] = pythonpath
235
240
236 usecorrectpython()
241 usecorrectpython()
237 global hgpkg
242 global hgpkg
238 hgpkg = _hgpath()
243 hgpkg = _hgpath()
239
244
240 vlog("# Installing dummy diffstat")
245 vlog("# Installing dummy diffstat")
241 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
246 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
242 f.write('#!' + sys.executable + '\n'
247 f.write('#!' + sys.executable + '\n'
243 'import sys\n'
248 'import sys\n'
244 'files = 0\n'
249 'files = 0\n'
245 'for line in sys.stdin:\n'
250 'for line in sys.stdin:\n'
246 ' if line.startswith("diff "):\n'
251 ' if line.startswith("diff "):\n'
247 ' files += 1\n'
252 ' files += 1\n'
248 'sys.stdout.write("files patched: %d\\n" % files)\n')
253 'sys.stdout.write("files patched: %d\\n" % files)\n')
249 f.close()
254 f.close()
250 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
255 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
251
256
252 if options.anycoverage:
257 if options.anycoverage:
253 vlog("# Installing coverage wrapper")
258 vlog("# Installing coverage wrapper")
254 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
259 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
255 if os.path.exists(COVERAGE_FILE):
260 if os.path.exists(COVERAGE_FILE):
256 os.unlink(COVERAGE_FILE)
261 os.unlink(COVERAGE_FILE)
257 # Create a wrapper script to invoke hg via coverage.py
262 # Create a wrapper script to invoke hg via coverage.py
258 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
263 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
259 f = open(os.path.join(BINDIR, 'hg'), 'w')
264 f = open(os.path.join(BINDIR, 'hg'), 'w')
260 f.write('#!' + sys.executable + '\n')
265 f.write('#!' + sys.executable + '\n')
261 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
266 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
262 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
267 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
263 (os.path.join(TESTDIR, 'coverage.py'),
268 (os.path.join(TESTDIR, 'coverage.py'),
264 os.path.join(BINDIR, '_hg.py')))
269 os.path.join(BINDIR, '_hg.py')))
265 f.close()
270 f.close()
266 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
271 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
267 PYTHON = '"%s" "%s" -x -p' % (sys.executable,
272 PYTHON = '"%s" "%s" -x -p' % (sys.executable,
268 os.path.join(TESTDIR, 'coverage.py'))
273 os.path.join(TESTDIR, 'coverage.py'))
269
274
270 def _hgpath():
275 def _hgpath():
271 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
276 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
272 hgpath = os.popen(cmd % PYTHON)
277 hgpath = os.popen(cmd % PYTHON)
273 path = hgpath.read().strip()
278 path = hgpath.read().strip()
274 hgpath.close()
279 hgpath.close()
275 return path
280 return path
276
281
277 def outputcoverage(options):
282 def outputcoverage(options):
278
283
279 vlog('# Producing coverage report')
284 vlog('# Producing coverage report')
280 os.chdir(PYTHONDIR)
285 os.chdir(PYTHONDIR)
281
286
282 def covrun(*args):
287 def covrun(*args):
283 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
288 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
284 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
289 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
285 vlog('# Running: %s' % cmd)
290 vlog('# Running: %s' % cmd)
286 os.system(cmd)
291 os.system(cmd)
287
292
288 omit = [BINDIR, TESTDIR, PYTHONDIR]
293 omit = [BINDIR, TESTDIR, PYTHONDIR]
289 if not options.cover_stdlib:
294 if not options.cover_stdlib:
290 # Exclude as system paths (ignoring empty strings seen on win)
295 # Exclude as system paths (ignoring empty strings seen on win)
291 omit += [x for x in sys.path if x != '']
296 omit += [x for x in sys.path if x != '']
292 omit = ','.join(omit)
297 omit = ','.join(omit)
293
298
294 covrun('-c') # combine from parallel processes
299 covrun('-c') # combine from parallel processes
295 covrun('-i', '-r', '"--omit=%s"' % omit) # report
300 covrun('-i', '-r', '"--omit=%s"' % omit) # report
296 if options.annotate:
301 if options.annotate:
297 adir = os.path.join(TESTDIR, 'annotated')
302 adir = os.path.join(TESTDIR, 'annotated')
298 if not os.path.isdir(adir):
303 if not os.path.isdir(adir):
299 os.mkdir(adir)
304 os.mkdir(adir)
300 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
305 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
301
306
302 class Timeout(Exception):
307 class Timeout(Exception):
303 pass
308 pass
304
309
305 def alarmed(signum, frame):
310 def alarmed(signum, frame):
306 raise Timeout
311 raise Timeout
307
312
308 def run(cmd, options):
313 def run(cmd, options):
309 """Run command in a sub-process, capturing the output (stdout and stderr).
314 """Run command in a sub-process, capturing the output (stdout and stderr).
310 Return the exist code, and output."""
315 Return the exist code, and output."""
311 # TODO: Use subprocess.Popen if we're running on Python 2.4
316 # TODO: Use subprocess.Popen if we're running on Python 2.4
312 if os.name == 'nt' or sys.platform.startswith('java'):
317 if os.name == 'nt' or sys.platform.startswith('java'):
313 tochild, fromchild = os.popen4(cmd)
318 tochild, fromchild = os.popen4(cmd)
314 tochild.close()
319 tochild.close()
315 output = fromchild.read()
320 output = fromchild.read()
316 ret = fromchild.close()
321 ret = fromchild.close()
317 if ret == None:
322 if ret == None:
318 ret = 0
323 ret = 0
319 else:
324 else:
320 proc = Popen4(cmd)
325 proc = Popen4(cmd)
321 try:
326 try:
322 output = ''
327 output = ''
323 proc.tochild.close()
328 proc.tochild.close()
324 output = proc.fromchild.read()
329 output = proc.fromchild.read()
325 ret = proc.wait()
330 ret = proc.wait()
326 if os.WIFEXITED(ret):
331 if os.WIFEXITED(ret):
327 ret = os.WEXITSTATUS(ret)
332 ret = os.WEXITSTATUS(ret)
328 except Timeout:
333 except Timeout:
329 vlog('# Process %d timed out - killing it' % proc.pid)
334 vlog('# Process %d timed out - killing it' % proc.pid)
330 os.kill(proc.pid, signal.SIGTERM)
335 os.kill(proc.pid, signal.SIGTERM)
331 ret = proc.wait()
336 ret = proc.wait()
332 if ret == 0:
337 if ret == 0:
333 ret = signal.SIGTERM << 8
338 ret = signal.SIGTERM << 8
334 output += ("\n### Abort: timeout after %d seconds.\n"
339 output += ("\n### Abort: timeout after %d seconds.\n"
335 % options.timeout)
340 % options.timeout)
336 return ret, splitnewlines(output)
341 return ret, splitnewlines(output)
337
342
338 def runone(options, test, skips, fails):
343 def runone(options, test, skips, fails):
339 '''tristate output:
344 '''tristate output:
340 None -> skipped
345 None -> skipped
341 True -> passed
346 True -> passed
342 False -> failed'''
347 False -> failed'''
343
348
344 def skip(msg):
349 def skip(msg):
345 if not options.verbose:
350 if not options.verbose:
346 skips.append((test, msg))
351 skips.append((test, msg))
347 else:
352 else:
348 print "\nSkipping %s: %s" % (test, msg)
353 print "\nSkipping %s: %s" % (test, msg)
349 return None
354 return None
350
355
351 def fail(msg):
356 def fail(msg):
352 fails.append((test, msg))
357 fails.append((test, msg))
353 if not options.nodiff:
358 if not options.nodiff:
354 print "\nERROR: %s %s" % (test, msg)
359 print "\nERROR: %s %s" % (test, msg)
355 return None
360 return None
356
361
357 vlog("# Test", test)
362 vlog("# Test", test)
358
363
359 # create a fresh hgrc
364 # create a fresh hgrc
360 hgrc = file(HGRCPATH, 'w+')
365 hgrc = file(HGRCPATH, 'w+')
361 hgrc.write('[ui]\n')
366 hgrc.write('[ui]\n')
362 hgrc.write('slash = True\n')
367 hgrc.write('slash = True\n')
363 hgrc.write('[defaults]\n')
368 hgrc.write('[defaults]\n')
364 hgrc.write('backout = -d "0 0"\n')
369 hgrc.write('backout = -d "0 0"\n')
365 hgrc.write('commit = -d "0 0"\n')
370 hgrc.write('commit = -d "0 0"\n')
366 hgrc.write('tag = -d "0 0"\n')
371 hgrc.write('tag = -d "0 0"\n')
367 hgrc.close()
372 hgrc.close()
368
373
369 err = os.path.join(TESTDIR, test+".err")
374 err = os.path.join(TESTDIR, test+".err")
370 ref = os.path.join(TESTDIR, test+".out")
375 ref = os.path.join(TESTDIR, test+".out")
371 testpath = os.path.join(TESTDIR, test)
376 testpath = os.path.join(TESTDIR, test)
372
377
373 if os.path.exists(err):
378 if os.path.exists(err):
374 os.remove(err) # Remove any previous output files
379 os.remove(err) # Remove any previous output files
375
380
376 # Make a tmp subdirectory to work in
381 # Make a tmp subdirectory to work in
377 tmpd = os.path.join(HGTMP, test)
382 tmpd = os.path.join(HGTMP, test)
378 os.mkdir(tmpd)
383 os.mkdir(tmpd)
379 os.chdir(tmpd)
384 os.chdir(tmpd)
380
385
381 try:
386 try:
382 tf = open(testpath)
387 tf = open(testpath)
383 firstline = tf.readline().rstrip()
388 firstline = tf.readline().rstrip()
384 tf.close()
389 tf.close()
385 except:
390 except:
386 firstline = ''
391 firstline = ''
387 lctest = test.lower()
392 lctest = test.lower()
388
393
389 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
394 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
390 cmd = '%s "%s"' % (PYTHON, testpath)
395 cmd = '%s "%s"' % (PYTHON, testpath)
391 elif lctest.endswith('.bat'):
396 elif lctest.endswith('.bat'):
392 # do not run batch scripts on non-windows
397 # do not run batch scripts on non-windows
393 if os.name != 'nt':
398 if os.name != 'nt':
394 return skip("batch script")
399 return skip("batch script")
395 # To reliably get the error code from batch files on WinXP,
400 # To reliably get the error code from batch files on WinXP,
396 # the "cmd /c call" prefix is needed. Grrr
401 # the "cmd /c call" prefix is needed. Grrr
397 cmd = 'cmd /c call "%s"' % testpath
402 cmd = 'cmd /c call "%s"' % testpath
398 else:
403 else:
399 # do not run shell scripts on windows
404 # do not run shell scripts on windows
400 if os.name == 'nt':
405 if os.name == 'nt':
401 return skip("shell script")
406 return skip("shell script")
402 # do not try to run non-executable programs
407 # do not try to run non-executable programs
403 if not os.path.exists(testpath):
408 if not os.path.exists(testpath):
404 return fail("does not exist")
409 return fail("does not exist")
405 elif not os.access(testpath, os.X_OK):
410 elif not os.access(testpath, os.X_OK):
406 return skip("not executable")
411 return skip("not executable")
407 cmd = '"%s"' % testpath
412 cmd = '"%s"' % testpath
408
413
409 if options.timeout > 0:
414 if options.timeout > 0:
410 signal.alarm(options.timeout)
415 signal.alarm(options.timeout)
411
416
412 vlog("# Running", cmd)
417 vlog("# Running", cmd)
413 ret, out = run(cmd, options)
418 ret, out = run(cmd, options)
414 vlog("# Ret was:", ret)
419 vlog("# Ret was:", ret)
415
420
416 if options.timeout > 0:
421 if options.timeout > 0:
417 signal.alarm(0)
422 signal.alarm(0)
418
423
419 mark = '.'
424 mark = '.'
420
425
421 skipped = (ret == SKIPPED_STATUS)
426 skipped = (ret == SKIPPED_STATUS)
422 # If reference output file exists, check test output against it
427 # If reference output file exists, check test output against it
423 if os.path.exists(ref):
428 if os.path.exists(ref):
424 f = open(ref, "r")
429 f = open(ref, "r")
425 refout = splitnewlines(f.read())
430 refout = splitnewlines(f.read())
426 f.close()
431 f.close()
427 else:
432 else:
428 refout = []
433 refout = []
429 if skipped:
434 if skipped:
430 mark = 's'
435 mark = 's'
431 missing, failed = parsehghaveoutput(out)
436 missing, failed = parsehghaveoutput(out)
432 if not missing:
437 if not missing:
433 missing = ['irrelevant']
438 missing = ['irrelevant']
434 if failed:
439 if failed:
435 fail("hghave failed checking for %s" % failed[-1])
440 fail("hghave failed checking for %s" % failed[-1])
436 skipped = False
441 skipped = False
437 else:
442 else:
438 skip(missing[-1])
443 skip(missing[-1])
439 elif out != refout:
444 elif out != refout:
440 mark = '!'
445 mark = '!'
441 if ret:
446 if ret:
442 fail("output changed and returned error code %d" % ret)
447 fail("output changed and returned error code %d" % ret)
443 else:
448 else:
444 fail("output changed")
449 fail("output changed")
445 if not options.nodiff:
450 if not options.nodiff:
446 showdiff(refout, out)
451 showdiff(refout, out)
447 ret = 1
452 ret = 1
448 elif ret:
453 elif ret:
449 mark = '!'
454 mark = '!'
450 fail("returned error code %d" % ret)
455 fail("returned error code %d" % ret)
451
456
452 if not options.verbose:
457 if not options.verbose:
453 sys.stdout.write(mark)
458 sys.stdout.write(mark)
454 sys.stdout.flush()
459 sys.stdout.flush()
455
460
456 if ret != 0 and not skipped:
461 if ret != 0 and not skipped:
457 # Save errors to a file for diagnosis
462 # Save errors to a file for diagnosis
458 f = open(err, "wb")
463 f = open(err, "wb")
459 for line in out:
464 for line in out:
460 f.write(line)
465 f.write(line)
461 f.close()
466 f.close()
462
467
463 # Kill off any leftover daemon processes
468 # Kill off any leftover daemon processes
464 try:
469 try:
465 fp = file(DAEMON_PIDS)
470 fp = file(DAEMON_PIDS)
466 for line in fp:
471 for line in fp:
467 try:
472 try:
468 pid = int(line)
473 pid = int(line)
469 except ValueError:
474 except ValueError:
470 continue
475 continue
471 try:
476 try:
472 os.kill(pid, 0)
477 os.kill(pid, 0)
473 vlog('# Killing daemon process %d' % pid)
478 vlog('# Killing daemon process %d' % pid)
474 os.kill(pid, signal.SIGTERM)
479 os.kill(pid, signal.SIGTERM)
475 time.sleep(0.25)
480 time.sleep(0.25)
476 os.kill(pid, 0)
481 os.kill(pid, 0)
477 vlog('# Daemon process %d is stuck - really killing it' % pid)
482 vlog('# Daemon process %d is stuck - really killing it' % pid)
478 os.kill(pid, signal.SIGKILL)
483 os.kill(pid, signal.SIGKILL)
479 except OSError, err:
484 except OSError, err:
480 if err.errno != errno.ESRCH:
485 if err.errno != errno.ESRCH:
481 raise
486 raise
482 fp.close()
487 fp.close()
483 os.unlink(DAEMON_PIDS)
488 os.unlink(DAEMON_PIDS)
484 except IOError:
489 except IOError:
485 pass
490 pass
486
491
487 os.chdir(TESTDIR)
492 os.chdir(TESTDIR)
488 if not options.keep_tmpdir:
493 if not options.keep_tmpdir:
489 shutil.rmtree(tmpd, True)
494 shutil.rmtree(tmpd, True)
490 if skipped:
495 if skipped:
491 return None
496 return None
492 return ret == 0
497 return ret == 0
493
498
494 def runchildren(options, expecthg, tests):
499 def runchildren(options, expecthg, tests):
495 if not options.with_hg:
500 if not options.with_hg:
496 installhg(options)
501 installhg(options)
497 if hgpkg != expecthg:
502 if hgpkg != expecthg:
498 print '# Testing unexpected mercurial: %s' % hgpkg
503 print '# Testing unexpected mercurial: %s' % hgpkg
499
504
500 optcopy = dict(options.__dict__)
505 optcopy = dict(options.__dict__)
501 optcopy['jobs'] = 1
506 optcopy['jobs'] = 1
502 optcopy['with_hg'] = INST
507 optcopy['with_hg'] = INST
503 opts = []
508 opts = []
504 for opt, value in optcopy.iteritems():
509 for opt, value in optcopy.iteritems():
505 name = '--' + opt.replace('_', '-')
510 name = '--' + opt.replace('_', '-')
506 if value is True:
511 if value is True:
507 opts.append(name)
512 opts.append(name)
508 elif value is not None:
513 elif value is not None:
509 opts.append(name + '=' + str(value))
514 opts.append(name + '=' + str(value))
510
515
511 tests.reverse()
516 tests.reverse()
512 jobs = [[] for j in xrange(options.jobs)]
517 jobs = [[] for j in xrange(options.jobs)]
513 while tests:
518 while tests:
514 for job in jobs:
519 for job in jobs:
515 if not tests: break
520 if not tests: break
516 job.append(tests.pop())
521 job.append(tests.pop())
517 fps = {}
522 fps = {}
518 for j, job in enumerate(jobs):
523 for j, job in enumerate(jobs):
519 if not job:
524 if not job:
520 continue
525 continue
521 rfd, wfd = os.pipe()
526 rfd, wfd = os.pipe()
522 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
527 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
523 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
528 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
524 vlog(' '.join(cmdline))
529 vlog(' '.join(cmdline))
525 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
530 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
526 os.close(wfd)
531 os.close(wfd)
527 failures = 0
532 failures = 0
528 tested, skipped, failed = 0, 0, 0
533 tested, skipped, failed = 0, 0, 0
529 skips = []
534 skips = []
530 fails = []
535 fails = []
531 while fps:
536 while fps:
532 pid, status = os.wait()
537 pid, status = os.wait()
533 fp = fps.pop(pid)
538 fp = fps.pop(pid)
534 l = fp.read().splitlines()
539 l = fp.read().splitlines()
535 test, skip, fail = map(int, l[:3])
540 test, skip, fail = map(int, l[:3])
536 split = -fail or len(l)
541 split = -fail or len(l)
537 for s in l[3:split]:
542 for s in l[3:split]:
538 skips.append(s.split(" ", 1))
543 skips.append(s.split(" ", 1))
539 for s in l[split:]:
544 for s in l[split:]:
540 fails.append(s.split(" ", 1))
545 fails.append(s.split(" ", 1))
541 tested += test
546 tested += test
542 skipped += skip
547 skipped += skip
543 failed += fail
548 failed += fail
544 vlog('pid %d exited, status %d' % (pid, status))
549 vlog('pid %d exited, status %d' % (pid, status))
545 failures |= status
550 failures |= status
546 print
551 print
547 for s in skips:
552 for s in skips:
548 print "Skipped %s: %s" % (s[0], s[1])
553 print "Skipped %s: %s" % (s[0], s[1])
549 for s in fails:
554 for s in fails:
550 print "Failed %s: %s" % (s[0], s[1])
555 print "Failed %s: %s" % (s[0], s[1])
551
556
552 if hgpkg != expecthg:
557 if hgpkg != expecthg:
553 print '# Tested unexpected mercurial: %s' % hgpkg
558 print '# Tested unexpected mercurial: %s' % hgpkg
554 print "# Ran %d tests, %d skipped, %d failed." % (
559 print "# Ran %d tests, %d skipped, %d failed." % (
555 tested, skipped, failed)
560 tested, skipped, failed)
556 sys.exit(failures != 0)
561 sys.exit(failures != 0)
557
562
558 def runtests(options, expecthg, tests):
563 def runtests(options, expecthg, tests):
559 global DAEMON_PIDS, HGRCPATH
564 global DAEMON_PIDS, HGRCPATH
560 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
565 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
561 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
566 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
562
567
563 try:
568 try:
564 if not options.with_hg:
569 if not options.with_hg:
565 installhg(options)
570 installhg(options)
566
571
567 if hgpkg != expecthg:
572 if hgpkg != expecthg:
568 print '# Testing unexpected mercurial: %s' % hgpkg
573 print '# Testing unexpected mercurial: %s' % hgpkg
569
574
570 if options.timeout > 0:
575 if options.timeout > 0:
571 try:
576 try:
572 signal.signal(signal.SIGALRM, alarmed)
577 signal.signal(signal.SIGALRM, alarmed)
573 vlog('# Running each test with %d second timeout' %
578 vlog('# Running each test with %d second timeout' %
574 options.timeout)
579 options.timeout)
575 except AttributeError:
580 except AttributeError:
576 print 'WARNING: cannot run tests with timeouts'
581 print 'WARNING: cannot run tests with timeouts'
577 options.timeout = 0
582 options.timeout = 0
578
583
579 tested = 0
584 tested = 0
580 failed = 0
585 failed = 0
581 skipped = 0
586 skipped = 0
582
587
583 if options.restart:
588 if options.restart:
584 orig = list(tests)
589 orig = list(tests)
585 while tests:
590 while tests:
586 if os.path.exists(tests[0] + ".err"):
591 if os.path.exists(tests[0] + ".err"):
587 break
592 break
588 tests.pop(0)
593 tests.pop(0)
589 if not tests:
594 if not tests:
590 print "running all tests"
595 print "running all tests"
591 tests = orig
596 tests = orig
592
597
593 skips = []
598 skips = []
594 fails = []
599 fails = []
595 for test in tests:
600 for test in tests:
596 if options.retest and not os.path.exists(test + ".err"):
601 if options.retest and not os.path.exists(test + ".err"):
597 skipped += 1
602 skipped += 1
598 continue
603 continue
599 ret = runone(options, test, skips, fails)
604 ret = runone(options, test, skips, fails)
600 if ret is None:
605 if ret is None:
601 skipped += 1
606 skipped += 1
602 elif not ret:
607 elif not ret:
603 if options.interactive:
608 if options.interactive:
604 print "Accept this change? [n] ",
609 print "Accept this change? [n] ",
605 answer = sys.stdin.readline().strip()
610 answer = sys.stdin.readline().strip()
606 if answer.lower() in "y yes".split():
611 if answer.lower() in "y yes".split():
607 rename(test + ".err", test + ".out")
612 rename(test + ".err", test + ".out")
608 tested += 1
613 tested += 1
609 fails.pop()
614 fails.pop()
610 continue
615 continue
611 failed += 1
616 failed += 1
612 if options.first:
617 if options.first:
613 break
618 break
614 tested += 1
619 tested += 1
615
620
616 if options.child:
621 if options.child:
617 fp = os.fdopen(options.child, 'w')
622 fp = os.fdopen(options.child, 'w')
618 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
623 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
619 for s in skips:
624 for s in skips:
620 fp.write("%s %s\n" % s)
625 fp.write("%s %s\n" % s)
621 for s in fails:
626 for s in fails:
622 fp.write("%s %s\n" % s)
627 fp.write("%s %s\n" % s)
623 fp.close()
628 fp.close()
624 else:
629 else:
625 print
630 print
626 for s in skips:
631 for s in skips:
627 print "Skipped %s: %s" % s
632 print "Skipped %s: %s" % s
628 for s in fails:
633 for s in fails:
629 print "Failed %s: %s" % s
634 print "Failed %s: %s" % s
630 if hgpkg != expecthg:
635 if hgpkg != expecthg:
631 print '# Tested unexpected mercurial: %s' % hgpkg
636 print '# Tested unexpected mercurial: %s' % hgpkg
632 print "# Ran %d tests, %d skipped, %d failed." % (
637 print "# Ran %d tests, %d skipped, %d failed." % (
633 tested, skipped, failed)
638 tested, skipped, failed)
634
639
635 if options.anycoverage:
640 if options.anycoverage:
636 outputcoverage(options)
641 outputcoverage(options)
637 except KeyboardInterrupt:
642 except KeyboardInterrupt:
638 failed = True
643 failed = True
639 print "\ninterrupted!"
644 print "\ninterrupted!"
640
645
641 if failed:
646 if failed:
642 sys.exit(1)
647 sys.exit(1)
643
648
644 def main():
649 def main():
645 (options, args) = parseargs()
650 (options, args) = parseargs()
646 if not options.child:
651 if not options.child:
647 os.umask(022)
652 os.umask(022)
648
653
649 checktools()
654 checktools()
650
655
651 # Reset some environment variables to well-known values so that
656 # Reset some environment variables to well-known values so that
652 # the tests produce repeatable output.
657 # the tests produce repeatable output.
653 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
658 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
654 os.environ['TZ'] = 'GMT'
659 os.environ['TZ'] = 'GMT'
655 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
660 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
656 os.environ['CDPATH'] = ''
661 os.environ['CDPATH'] = ''
657
662
658 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
663 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
659 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
664 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
660 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
665 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
661 options.tmpdir))
666 options.tmpdir))
662 DAEMON_PIDS = None
667 DAEMON_PIDS = None
663 HGRCPATH = None
668 HGRCPATH = None
664
669
665 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
670 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
666 os.environ["HGMERGE"] = "internal:merge"
671 os.environ["HGMERGE"] = "internal:merge"
667 os.environ["HGUSER"] = "test"
672 os.environ["HGUSER"] = "test"
668 os.environ["HGENCODING"] = "ascii"
673 os.environ["HGENCODING"] = "ascii"
669 os.environ["HGENCODINGMODE"] = "strict"
674 os.environ["HGENCODINGMODE"] = "strict"
670 os.environ["HGPORT"] = str(options.port)
675 os.environ["HGPORT"] = str(options.port)
671 os.environ["HGPORT1"] = str(options.port + 1)
676 os.environ["HGPORT1"] = str(options.port + 1)
672 os.environ["HGPORT2"] = str(options.port + 2)
677 os.environ["HGPORT2"] = str(options.port + 2)
673
678
674 if options.with_hg:
679 if options.with_hg:
675 INST = options.with_hg
680 INST = options.with_hg
676 else:
681 else:
677 INST = os.path.join(HGTMP, "install")
682 INST = os.path.join(HGTMP, "install")
678 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
683 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
679 PYTHONDIR = os.path.join(INST, "lib", "python")
684 PYTHONDIR = os.path.join(INST, "lib", "python")
680 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
685 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
681
686
682 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
687 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
683
688
684 if len(args) == 0:
689 if len(args) == 0:
685 args = os.listdir(".")
690 args = os.listdir(".")
686 args.sort()
691 args.sort()
687
692
688 tests = []
693 tests = []
689 for test in args:
694 for test in args:
690 if (test.startswith("test-") and '~' not in test and
695 if (test.startswith("test-") and '~' not in test and
691 ('.' not in test or test.endswith('.py') or
696 ('.' not in test or test.endswith('.py') or
692 test.endswith('.bat'))):
697 test.endswith('.bat'))):
693 tests.append(test)
698 tests.append(test)
694 if not tests:
699 if not tests:
695 print "# Ran 0 tests, 0 skipped, 0 failed."
700 print "# Ran 0 tests, 0 skipped, 0 failed."
696 return
701 return
697
702
698 vlog("# Using TESTDIR", TESTDIR)
703 vlog("# Using TESTDIR", TESTDIR)
699 vlog("# Using HGTMP", HGTMP)
704 vlog("# Using HGTMP", HGTMP)
700
705
701 try:
706 try:
702 if len(tests) > 1 and options.jobs > 1:
707 if len(tests) > 1 and options.jobs > 1:
703 runchildren(options, expecthg, tests)
708 runchildren(options, expecthg, tests)
704 else:
709 else:
705 runtests(options, expecthg, tests)
710 runtests(options, expecthg, tests)
706 finally:
711 finally:
707 cleanup(options)
712 cleanup(options)
708
713
709 main()
714 main()
General Comments 0
You need to be logged in to leave comments. Login now