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