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