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