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