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