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