##// END OF EJS Templates
run-tests: factor out main(); reduce use of globals a bit.
Greg Ward -
r8094:60a9e3cf default
parent child Browse files
Show More
@@ -1,709 +1,713 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(options):
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(options):
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():
277 def _hgpath():
278 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
278 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
279 hgpath = os.popen(cmd % python)
279 hgpath = os.popen(cmd % python)
280 path = hgpath.read().strip()
280 path = hgpath.read().strip()
281 hgpath.close()
281 hgpath.close()
282 return path
282 return path
283
283
284 def output_coverage():
284 def output_coverage(options):
285 vlog("# Producing coverage report")
285 vlog("# Producing coverage report")
286 omit = [BINDIR, TESTDIR, PYTHONDIR]
286 omit = [BINDIR, TESTDIR, PYTHONDIR]
287 if not options.cover_stdlib:
287 if not options.cover_stdlib:
288 # Exclude as system paths (ignoring empty strings seen on win)
288 # Exclude as system paths (ignoring empty strings seen on win)
289 omit += [x for x in sys.path if x != '']
289 omit += [x for x in sys.path if x != '']
290 omit = ','.join(omit)
290 omit = ','.join(omit)
291 os.chdir(PYTHONDIR)
291 os.chdir(PYTHONDIR)
292 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
292 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
293 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
293 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
294 vlog("# Running: "+cmd)
294 vlog("# Running: "+cmd)
295 os.system(cmd)
295 os.system(cmd)
296 if options.annotate:
296 if options.annotate:
297 adir = os.path.join(TESTDIR, 'annotated')
297 adir = os.path.join(TESTDIR, 'annotated')
298 if not os.path.isdir(adir):
298 if not os.path.isdir(adir):
299 os.mkdir(adir)
299 os.mkdir(adir)
300 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
300 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
301 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
301 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
302 adir, omit)
302 adir, omit)
303 vlog("# Running: "+cmd)
303 vlog("# Running: "+cmd)
304 os.system(cmd)
304 os.system(cmd)
305
305
306 class Timeout(Exception):
306 class Timeout(Exception):
307 pass
307 pass
308
308
309 def alarmed(signum, frame):
309 def alarmed(signum, frame):
310 raise Timeout
310 raise Timeout
311
311
312 def run(cmd):
312 def run(cmd):
313 """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).
314 Return the exist code, and output."""
314 Return the exist code, and output."""
315 # 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
316 if os.name == 'nt' or sys.platform.startswith('java'):
316 if os.name == 'nt' or sys.platform.startswith('java'):
317 tochild, fromchild = os.popen4(cmd)
317 tochild, fromchild = os.popen4(cmd)
318 tochild.close()
318 tochild.close()
319 output = fromchild.read()
319 output = fromchild.read()
320 ret = fromchild.close()
320 ret = fromchild.close()
321 if ret == None:
321 if ret == None:
322 ret = 0
322 ret = 0
323 else:
323 else:
324 proc = Popen4(cmd)
324 proc = Popen4(cmd)
325 try:
325 try:
326 output = ''
326 output = ''
327 proc.tochild.close()
327 proc.tochild.close()
328 output = proc.fromchild.read()
328 output = proc.fromchild.read()
329 ret = proc.wait()
329 ret = proc.wait()
330 if os.WIFEXITED(ret):
330 if os.WIFEXITED(ret):
331 ret = os.WEXITSTATUS(ret)
331 ret = os.WEXITSTATUS(ret)
332 except Timeout:
332 except Timeout:
333 vlog('# Process %d timed out - killing it' % proc.pid)
333 vlog('# Process %d timed out - killing it' % proc.pid)
334 os.kill(proc.pid, signal.SIGTERM)
334 os.kill(proc.pid, signal.SIGTERM)
335 ret = proc.wait()
335 ret = proc.wait()
336 if ret == 0:
336 if ret == 0:
337 ret = signal.SIGTERM << 8
337 ret = signal.SIGTERM << 8
338 output += ("\n### Abort: timeout after %d seconds.\n"
338 output += ("\n### Abort: timeout after %d seconds.\n"
339 % options.timeout)
339 % options.timeout)
340 return ret, splitnewlines(output)
340 return ret, splitnewlines(output)
341
341
342 def run_one(test, skips, fails):
342 def run_one(options, test, skips, fails):
343 '''tristate output:
343 '''tristate output:
344 None -> skipped
344 None -> skipped
345 True -> passed
345 True -> passed
346 False -> failed'''
346 False -> failed'''
347
347
348 def skip(msg):
348 def skip(msg):
349 if not verbose:
349 if not verbose:
350 skips.append((test, msg))
350 skips.append((test, msg))
351 else:
351 else:
352 print "\nSkipping %s: %s" % (test, msg)
352 print "\nSkipping %s: %s" % (test, msg)
353 return None
353 return None
354
354
355 def fail(msg):
355 def fail(msg):
356 fails.append((test, msg))
356 fails.append((test, msg))
357 if not nodiff:
357 if not nodiff:
358 print "\nERROR: %s %s" % (test, msg)
358 print "\nERROR: %s %s" % (test, msg)
359 return None
359 return None
360
360
361 vlog("# Test", test)
361 vlog("# Test", test)
362
362
363 # create a fresh hgrc
363 # create a fresh hgrc
364 hgrc = file(HGRCPATH, 'w+')
364 hgrc = file(HGRCPATH, 'w+')
365 hgrc.write('[ui]\n')
365 hgrc.write('[ui]\n')
366 hgrc.write('slash = True\n')
366 hgrc.write('slash = True\n')
367 hgrc.write('[defaults]\n')
367 hgrc.write('[defaults]\n')
368 hgrc.write('backout = -d "0 0"\n')
368 hgrc.write('backout = -d "0 0"\n')
369 hgrc.write('commit = -d "0 0"\n')
369 hgrc.write('commit = -d "0 0"\n')
370 hgrc.write('debugrawcommit = -d "0 0"\n')
370 hgrc.write('debugrawcommit = -d "0 0"\n')
371 hgrc.write('tag = -d "0 0"\n')
371 hgrc.write('tag = -d "0 0"\n')
372 hgrc.close()
372 hgrc.close()
373
373
374 err = os.path.join(TESTDIR, test+".err")
374 err = os.path.join(TESTDIR, test+".err")
375 ref = os.path.join(TESTDIR, test+".out")
375 ref = os.path.join(TESTDIR, test+".out")
376 testpath = os.path.join(TESTDIR, test)
376 testpath = os.path.join(TESTDIR, test)
377
377
378 if os.path.exists(err):
378 if os.path.exists(err):
379 os.remove(err) # Remove any previous output files
379 os.remove(err) # Remove any previous output files
380
380
381 # Make a tmp subdirectory to work in
381 # Make a tmp subdirectory to work in
382 tmpd = os.path.join(HGTMP, test)
382 tmpd = os.path.join(HGTMP, test)
383 os.mkdir(tmpd)
383 os.mkdir(tmpd)
384 os.chdir(tmpd)
384 os.chdir(tmpd)
385
385
386 try:
386 try:
387 tf = open(testpath)
387 tf = open(testpath)
388 firstline = tf.readline().rstrip()
388 firstline = tf.readline().rstrip()
389 tf.close()
389 tf.close()
390 except:
390 except:
391 firstline = ''
391 firstline = ''
392 lctest = test.lower()
392 lctest = test.lower()
393
393
394 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
394 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
395 cmd = '%s "%s"' % (python, testpath)
395 cmd = '%s "%s"' % (python, testpath)
396 elif lctest.endswith('.bat'):
396 elif lctest.endswith('.bat'):
397 # do not run batch scripts on non-windows
397 # do not run batch scripts on non-windows
398 if os.name != 'nt':
398 if os.name != 'nt':
399 return skip("batch script")
399 return skip("batch script")
400 # To reliably get the error code from batch files on WinXP,
400 # To reliably get the error code from batch files on WinXP,
401 # the "cmd /c call" prefix is needed. Grrr
401 # the "cmd /c call" prefix is needed. Grrr
402 cmd = 'cmd /c call "%s"' % testpath
402 cmd = 'cmd /c call "%s"' % testpath
403 else:
403 else:
404 # do not run shell scripts on windows
404 # do not run shell scripts on windows
405 if os.name == 'nt':
405 if os.name == 'nt':
406 return skip("shell script")
406 return skip("shell script")
407 # do not try to run non-executable programs
407 # do not try to run non-executable programs
408 if not os.path.exists(testpath):
408 if not os.path.exists(testpath):
409 return fail("does not exist")
409 return fail("does not exist")
410 elif not os.access(testpath, os.X_OK):
410 elif not os.access(testpath, os.X_OK):
411 return skip("not executable")
411 return skip("not executable")
412 cmd = '"%s"' % testpath
412 cmd = '"%s"' % testpath
413
413
414 if options.timeout > 0:
414 if options.timeout > 0:
415 signal.alarm(options.timeout)
415 signal.alarm(options.timeout)
416
416
417 vlog("# Running", cmd)
417 vlog("# Running", cmd)
418 ret, out = run(cmd)
418 ret, out = run(cmd)
419 vlog("# Ret was:", ret)
419 vlog("# Ret was:", ret)
420
420
421 if options.timeout > 0:
421 if options.timeout > 0:
422 signal.alarm(0)
422 signal.alarm(0)
423
423
424 mark = '.'
424 mark = '.'
425
425
426 skipped = (ret == SKIPPED_STATUS)
426 skipped = (ret == SKIPPED_STATUS)
427 # If reference output file exists, check test output against it
427 # If reference output file exists, check test output against it
428 if os.path.exists(ref):
428 if os.path.exists(ref):
429 f = open(ref, "r")
429 f = open(ref, "r")
430 ref_out = splitnewlines(f.read())
430 ref_out = splitnewlines(f.read())
431 f.close()
431 f.close()
432 else:
432 else:
433 ref_out = []
433 ref_out = []
434 if skipped:
434 if skipped:
435 mark = 's'
435 mark = 's'
436 missing, failed = parse_hghave_output(out)
436 missing, failed = parse_hghave_output(out)
437 if not missing:
437 if not missing:
438 missing = ['irrelevant']
438 missing = ['irrelevant']
439 if failed:
439 if failed:
440 fail("hghave failed checking for %s" % failed[-1])
440 fail("hghave failed checking for %s" % failed[-1])
441 skipped = False
441 skipped = False
442 else:
442 else:
443 skip(missing[-1])
443 skip(missing[-1])
444 elif out != ref_out:
444 elif out != ref_out:
445 mark = '!'
445 mark = '!'
446 if ret:
446 if ret:
447 fail("output changed and returned error code %d" % ret)
447 fail("output changed and returned error code %d" % ret)
448 else:
448 else:
449 fail("output changed")
449 fail("output changed")
450 if not nodiff:
450 if not nodiff:
451 show_diff(ref_out, out)
451 show_diff(ref_out, out)
452 ret = 1
452 ret = 1
453 elif ret:
453 elif ret:
454 mark = '!'
454 mark = '!'
455 fail("returned error code %d" % ret)
455 fail("returned error code %d" % ret)
456
456
457 if not verbose:
457 if not verbose:
458 sys.stdout.write(mark)
458 sys.stdout.write(mark)
459 sys.stdout.flush()
459 sys.stdout.flush()
460
460
461 if ret != 0 and not skipped:
461 if ret != 0 and not skipped:
462 # Save errors to a file for diagnosis
462 # Save errors to a file for diagnosis
463 f = open(err, "wb")
463 f = open(err, "wb")
464 for line in out:
464 for line in out:
465 f.write(line)
465 f.write(line)
466 f.close()
466 f.close()
467
467
468 # Kill off any leftover daemon processes
468 # Kill off any leftover daemon processes
469 try:
469 try:
470 fp = file(DAEMON_PIDS)
470 fp = file(DAEMON_PIDS)
471 for line in fp:
471 for line in fp:
472 try:
472 try:
473 pid = int(line)
473 pid = int(line)
474 except ValueError:
474 except ValueError:
475 continue
475 continue
476 try:
476 try:
477 os.kill(pid, 0)
477 os.kill(pid, 0)
478 vlog('# Killing daemon process %d' % pid)
478 vlog('# Killing daemon process %d' % pid)
479 os.kill(pid, signal.SIGTERM)
479 os.kill(pid, signal.SIGTERM)
480 time.sleep(0.25)
480 time.sleep(0.25)
481 os.kill(pid, 0)
481 os.kill(pid, 0)
482 vlog('# Daemon process %d is stuck - really killing it' % pid)
482 vlog('# Daemon process %d is stuck - really killing it' % pid)
483 os.kill(pid, signal.SIGKILL)
483 os.kill(pid, signal.SIGKILL)
484 except OSError, err:
484 except OSError, err:
485 if err.errno != errno.ESRCH:
485 if err.errno != errno.ESRCH:
486 raise
486 raise
487 fp.close()
487 fp.close()
488 os.unlink(DAEMON_PIDS)
488 os.unlink(DAEMON_PIDS)
489 except IOError:
489 except IOError:
490 pass
490 pass
491
491
492 os.chdir(TESTDIR)
492 os.chdir(TESTDIR)
493 if not options.keep_tmpdir:
493 if not options.keep_tmpdir:
494 shutil.rmtree(tmpd, True)
494 shutil.rmtree(tmpd, True)
495 if skipped:
495 if skipped:
496 return None
496 return None
497 return ret == 0
497 return ret == 0
498
498
499 def run_children(tests):
499 def run_children(options, expecthg, tests):
500 if not options.with_hg:
500 if not options.with_hg:
501 install_hg()
501 install_hg()
502 if hgpkg != expecthg:
502 if hgpkg != expecthg:
503 print '# Testing unexpected mercurial: %s' % hgpkg
503 print '# Testing unexpected mercurial: %s' % hgpkg
504
504
505 optcopy = dict(options.__dict__)
505 optcopy = dict(options.__dict__)
506 optcopy['jobs'] = 1
506 optcopy['jobs'] = 1
507 optcopy['with_hg'] = INST
507 optcopy['with_hg'] = INST
508 opts = []
508 opts = []
509 for opt, value in optcopy.iteritems():
509 for opt, value in optcopy.iteritems():
510 name = '--' + opt.replace('_', '-')
510 name = '--' + opt.replace('_', '-')
511 if value is True:
511 if value is True:
512 opts.append(name)
512 opts.append(name)
513 elif value is not None:
513 elif value is not None:
514 opts.append(name + '=' + str(value))
514 opts.append(name + '=' + str(value))
515
515
516 tests.reverse()
516 tests.reverse()
517 jobs = [[] for j in xrange(options.jobs)]
517 jobs = [[] for j in xrange(options.jobs)]
518 while tests:
518 while tests:
519 for j in xrange(options.jobs):
519 for j in xrange(options.jobs):
520 if not tests: break
520 if not tests: break
521 jobs[j].append(tests.pop())
521 jobs[j].append(tests.pop())
522 fps = {}
522 fps = {}
523 for j in xrange(len(jobs)):
523 for j in xrange(len(jobs)):
524 job = jobs[j]
524 job = jobs[j]
525 if not job:
525 if not job:
526 continue
526 continue
527 rfd, wfd = os.pipe()
527 rfd, wfd = os.pipe()
528 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
528 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
529 cmdline = [python, sys.argv[0]] + opts + childopts + job
529 cmdline = [python, sys.argv[0]] + opts + childopts + job
530 vlog(' '.join(cmdline))
530 vlog(' '.join(cmdline))
531 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
531 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
532 os.close(wfd)
532 os.close(wfd)
533 failures = 0
533 failures = 0
534 tested, skipped, failed = 0, 0, 0
534 tested, skipped, failed = 0, 0, 0
535 skips = []
535 skips = []
536 fails = []
536 fails = []
537 while fps:
537 while fps:
538 pid, status = os.wait()
538 pid, status = os.wait()
539 fp = fps.pop(pid)
539 fp = fps.pop(pid)
540 l = fp.read().splitlines()
540 l = fp.read().splitlines()
541 test, skip, fail = map(int, l[:3])
541 test, skip, fail = map(int, l[:3])
542 split = -fail or len(l)
542 split = -fail or len(l)
543 for s in l[3:split]:
543 for s in l[3:split]:
544 skips.append(s.split(" ", 1))
544 skips.append(s.split(" ", 1))
545 for s in l[split:]:
545 for s in l[split:]:
546 fails.append(s.split(" ", 1))
546 fails.append(s.split(" ", 1))
547 tested += test
547 tested += test
548 skipped += skip
548 skipped += skip
549 failed += fail
549 failed += fail
550 vlog('pid %d exited, status %d' % (pid, status))
550 vlog('pid %d exited, status %d' % (pid, status))
551 failures |= status
551 failures |= status
552 print
552 print
553 for s in skips:
553 for s in skips:
554 print "Skipped %s: %s" % (s[0], s[1])
554 print "Skipped %s: %s" % (s[0], s[1])
555 for s in fails:
555 for s in fails:
556 print "Failed %s: %s" % (s[0], s[1])
556 print "Failed %s: %s" % (s[0], s[1])
557
557
558 if hgpkg != expecthg:
558 if hgpkg != expecthg:
559 print '# Tested unexpected mercurial: %s' % hgpkg
559 print '# Tested unexpected mercurial: %s' % hgpkg
560 print "# Ran %d tests, %d skipped, %d failed." % (
560 print "# Ran %d tests, %d skipped, %d failed." % (
561 tested, skipped, failed)
561 tested, skipped, failed)
562 sys.exit(failures != 0)
562 sys.exit(failures != 0)
563
563
564 def run_tests(tests):
564 def run_tests(options, expecthg, tests):
565 global DAEMON_PIDS, HGRCPATH
565 global DAEMON_PIDS, HGRCPATH
566 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
566 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
567 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
567 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
568
568
569 try:
569 try:
570 if not options.with_hg:
570 if not options.with_hg:
571 install_hg()
571 install_hg(options)
572
572
573 if hgpkg != expecthg:
573 if hgpkg != expecthg:
574 print '# Testing unexpected mercurial: %s' % hgpkg
574 print '# Testing unexpected mercurial: %s' % hgpkg
575
575
576 if options.timeout > 0:
576 if options.timeout > 0:
577 try:
577 try:
578 signal.signal(signal.SIGALRM, alarmed)
578 signal.signal(signal.SIGALRM, alarmed)
579 vlog('# Running tests with %d-second timeout' %
579 vlog('# Running tests with %d-second timeout' %
580 options.timeout)
580 options.timeout)
581 except AttributeError:
581 except AttributeError:
582 print 'WARNING: cannot run tests with timeouts'
582 print 'WARNING: cannot run tests with timeouts'
583 options.timeout = 0
583 options.timeout = 0
584
584
585 tested = 0
585 tested = 0
586 failed = 0
586 failed = 0
587 skipped = 0
587 skipped = 0
588
588
589 if options.restart:
589 if options.restart:
590 orig = list(tests)
590 orig = list(tests)
591 while tests:
591 while tests:
592 if os.path.exists(tests[0] + ".err"):
592 if os.path.exists(tests[0] + ".err"):
593 break
593 break
594 tests.pop(0)
594 tests.pop(0)
595 if not tests:
595 if not tests:
596 print "running all tests"
596 print "running all tests"
597 tests = orig
597 tests = orig
598
598
599 skips = []
599 skips = []
600 fails = []
600 fails = []
601 for test in tests:
601 for test in tests:
602 if options.retest and not os.path.exists(test + ".err"):
602 if options.retest and not os.path.exists(test + ".err"):
603 skipped += 1
603 skipped += 1
604 continue
604 continue
605 ret = run_one(test, skips, fails)
605 ret = run_one(options, test, skips, fails)
606 if ret is None:
606 if ret is None:
607 skipped += 1
607 skipped += 1
608 elif not ret:
608 elif not ret:
609 if options.interactive:
609 if options.interactive:
610 print "Accept this change? [n] ",
610 print "Accept this change? [n] ",
611 answer = sys.stdin.readline().strip()
611 answer = sys.stdin.readline().strip()
612 if answer.lower() in "y yes".split():
612 if answer.lower() in "y yes".split():
613 rename(test + ".err", test + ".out")
613 rename(test + ".err", test + ".out")
614 tested += 1
614 tested += 1
615 fails.pop()
615 fails.pop()
616 continue
616 continue
617 failed += 1
617 failed += 1
618 if options.first:
618 if options.first:
619 break
619 break
620 tested += 1
620 tested += 1
621
621
622 if options.child:
622 if options.child:
623 fp = os.fdopen(options.child, 'w')
623 fp = os.fdopen(options.child, 'w')
624 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
624 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
625 for s in skips:
625 for s in skips:
626 fp.write("%s %s\n" % s)
626 fp.write("%s %s\n" % s)
627 for s in fails:
627 for s in fails:
628 fp.write("%s %s\n" % s)
628 fp.write("%s %s\n" % s)
629 fp.close()
629 fp.close()
630 else:
630 else:
631 print
631 print
632 for s in skips:
632 for s in skips:
633 print "Skipped %s: %s" % s
633 print "Skipped %s: %s" % s
634 for s in fails:
634 for s in fails:
635 print "Failed %s: %s" % s
635 print "Failed %s: %s" % s
636 if hgpkg != expecthg:
636 if hgpkg != expecthg:
637 print '# Tested unexpected mercurial: %s' % hgpkg
637 print '# Tested unexpected mercurial: %s' % hgpkg
638 print "# Ran %d tests, %d skipped, %d failed." % (
638 print "# Ran %d tests, %d skipped, %d failed." % (
639 tested, skipped, failed)
639 tested, skipped, failed)
640
640
641 if coverage:
641 if coverage:
642 output_coverage()
642 output_coverage(options)
643 except KeyboardInterrupt:
643 except KeyboardInterrupt:
644 failed = True
644 failed = True
645 print "\ninterrupted!"
645 print "\ninterrupted!"
646
646
647 if failed:
647 if failed:
648 sys.exit(1)
648 sys.exit(1)
649
649
650 def main():
650 (options, args) = parse_args()
651 (options, args) = parse_args()
651 if not options.child:
652 if not options.child:
652 os.umask(022)
653 os.umask(022)
653
654
654 check_required_tools()
655 check_required_tools()
655
656
656 # Reset some environment variables to well-known values so that
657 # Reset some environment variables to well-known values so that
657 # the tests produce repeatable output.
658 # the tests produce repeatable output.
658 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
659 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
659 os.environ['TZ'] = 'GMT'
660 os.environ['TZ'] = 'GMT'
660 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
661 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
661 os.environ['CDPATH'] = ''
662 os.environ['CDPATH'] = ''
662
663
664 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
663 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
665 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
664 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
666 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
665 options.tmpdir))
667 options.tmpdir))
666 DAEMON_PIDS = None
668 DAEMON_PIDS = None
667 HGRCPATH = None
669 HGRCPATH = None
668
670
669 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
671 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
670 os.environ["HGMERGE"] = "internal:merge"
672 os.environ["HGMERGE"] = "internal:merge"
671 os.environ["HGUSER"] = "test"
673 os.environ["HGUSER"] = "test"
672 os.environ["HGENCODING"] = "ascii"
674 os.environ["HGENCODING"] = "ascii"
673 os.environ["HGENCODINGMODE"] = "strict"
675 os.environ["HGENCODINGMODE"] = "strict"
674 os.environ["HGPORT"] = str(options.port)
676 os.environ["HGPORT"] = str(options.port)
675 os.environ["HGPORT1"] = str(options.port + 1)
677 os.environ["HGPORT1"] = str(options.port + 1)
676 os.environ["HGPORT2"] = str(options.port + 2)
678 os.environ["HGPORT2"] = str(options.port + 2)
677
679
678 if options.with_hg:
680 if options.with_hg:
679 INST = options.with_hg
681 INST = options.with_hg
680 else:
682 else:
681 INST = os.path.join(HGTMP, "install")
683 INST = os.path.join(HGTMP, "install")
682 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
684 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
683 PYTHONDIR = os.path.join(INST, "lib", "python")
685 PYTHONDIR = os.path.join(INST, "lib", "python")
684 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
686 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
685
687
686 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
688 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
687 hgpkg = None
689 hgpkg = None
688
690
689 if len(args) == 0:
691 if len(args) == 0:
690 args = os.listdir(".")
692 args = os.listdir(".")
691 args.sort()
693 args.sort()
692
694
693 tests = []
695 tests = []
694 for test in args:
696 for test in args:
695 if (test.startswith("test-") and '~' not in test and
697 if (test.startswith("test-") and '~' not in test and
696 ('.' not in test or test.endswith('.py') or
698 ('.' not in test or test.endswith('.py') or
697 test.endswith('.bat'))):
699 test.endswith('.bat'))):
698 tests.append(test)
700 tests.append(test)
699
701
700 vlog("# Using TESTDIR", TESTDIR)
702 vlog("# Using TESTDIR", TESTDIR)
701 vlog("# Using HGTMP", HGTMP)
703 vlog("# Using HGTMP", HGTMP)
702
704
703 try:
705 try:
704 if len(tests) > 1 and options.jobs > 1:
706 if len(tests) > 1 and options.jobs > 1:
705 run_children(tests)
707 run_children(options, expecthg, tests)
706 else:
708 else:
707 run_tests(tests)
709 run_tests(options, expecthg, tests)
708 finally:
710 finally:
709 cleanup_exit()
711 cleanup_exit(options)
712
713 main()
General Comments 0
You need to be logged in to leave comments. Login now