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