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