##// END OF EJS Templates
imported patch test-check
Dirkjan Ochtman -
r6982:9fc5bf4a default
parent child Browse files
Show More
@@ -1,620 +1,640 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 --home="%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
206 hgpkg = _hgpath()
205
207
206 if coverage:
208 if coverage:
207 vlog("# Installing coverage wrapper")
209 vlog("# Installing coverage wrapper")
208 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
210 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
209 if os.path.exists(COVERAGE_FILE):
211 if os.path.exists(COVERAGE_FILE):
210 os.unlink(COVERAGE_FILE)
212 os.unlink(COVERAGE_FILE)
211 # Create a wrapper script to invoke hg via coverage.py
213 # Create a wrapper script to invoke hg via coverage.py
212 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"))
213 f = open(os.path.join(BINDIR, 'hg'), 'w')
215 f = open(os.path.join(BINDIR, 'hg'), 'w')
214 f.write('#!' + sys.executable + '\n')
216 f.write('#!' + sys.executable + '\n')
215 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
217 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
216 '"%s", "-x", "%s"] + sys.argv[1:])\n' %
218 '"%s", "-x", "%s"] + sys.argv[1:])\n' %
217 (os.path.join(TESTDIR, 'coverage.py'),
219 (os.path.join(TESTDIR, 'coverage.py'),
218 os.path.join(BINDIR, '_hg.py')))
220 os.path.join(BINDIR, '_hg.py')))
219 f.close()
221 f.close()
220 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
222 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
221 python = '"%s" "%s" -x' % (sys.executable,
223 python = '"%s" "%s" -x' % (sys.executable,
222 os.path.join(TESTDIR,'coverage.py'))
224 os.path.join(TESTDIR,'coverage.py'))
223
225
224 def output_coverage():
226 def output_coverage():
225 vlog("# Producing coverage report")
227 vlog("# Producing coverage report")
226 omit = [BINDIR, TESTDIR, PYTHONDIR]
228 omit = [BINDIR, TESTDIR, PYTHONDIR]
227 if not options.cover_stdlib:
229 if not options.cover_stdlib:
228 # Exclude as system paths (ignoring empty strings seen on win)
230 # Exclude as system paths (ignoring empty strings seen on win)
229 omit += [x for x in sys.path if x != '']
231 omit += [x for x in sys.path if x != '']
230 omit = ','.join(omit)
232 omit = ','.join(omit)
231 os.chdir(PYTHONDIR)
233 os.chdir(PYTHONDIR)
232 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
234 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
233 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
235 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
234 vlog("# Running: "+cmd)
236 vlog("# Running: "+cmd)
235 os.system(cmd)
237 os.system(cmd)
236 if options.annotate:
238 if options.annotate:
237 adir = os.path.join(TESTDIR, 'annotated')
239 adir = os.path.join(TESTDIR, 'annotated')
238 if not os.path.isdir(adir):
240 if not os.path.isdir(adir):
239 os.mkdir(adir)
241 os.mkdir(adir)
240 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
242 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
241 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
243 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
242 adir, omit)
244 adir, omit)
243 vlog("# Running: "+cmd)
245 vlog("# Running: "+cmd)
244 os.system(cmd)
246 os.system(cmd)
245
247
246 class Timeout(Exception):
248 class Timeout(Exception):
247 pass
249 pass
248
250
249 def alarmed(signum, frame):
251 def alarmed(signum, frame):
250 raise Timeout
252 raise Timeout
251
253
252 def run(cmd):
254 def run(cmd):
253 """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).
254 Return the exist code, and output."""
256 Return the exist code, and output."""
255 # 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
256 if os.name == 'nt':
258 if os.name == 'nt':
257 tochild, fromchild = os.popen4(cmd)
259 tochild, fromchild = os.popen4(cmd)
258 tochild.close()
260 tochild.close()
259 output = fromchild.read()
261 output = fromchild.read()
260 ret = fromchild.close()
262 ret = fromchild.close()
261 if ret == None:
263 if ret == None:
262 ret = 0
264 ret = 0
263 else:
265 else:
264 proc = popen2.Popen4(cmd)
266 proc = popen2.Popen4(cmd)
265 try:
267 try:
266 output = ''
268 output = ''
267 proc.tochild.close()
269 proc.tochild.close()
268 output = proc.fromchild.read()
270 output = proc.fromchild.read()
269 ret = proc.wait()
271 ret = proc.wait()
270 if os.WIFEXITED(ret):
272 if os.WIFEXITED(ret):
271 ret = os.WEXITSTATUS(ret)
273 ret = os.WEXITSTATUS(ret)
272 except Timeout:
274 except Timeout:
273 vlog('# Process %d timed out - killing it' % proc.pid)
275 vlog('# Process %d timed out - killing it' % proc.pid)
274 os.kill(proc.pid, signal.SIGTERM)
276 os.kill(proc.pid, signal.SIGTERM)
275 ret = proc.wait()
277 ret = proc.wait()
276 if ret == 0:
278 if ret == 0:
277 ret = signal.SIGTERM << 8
279 ret = signal.SIGTERM << 8
278 output += ("\n### Abort: timeout after %d seconds.\n"
280 output += ("\n### Abort: timeout after %d seconds.\n"
279 % options.timeout)
281 % options.timeout)
280 return ret, splitnewlines(output)
282 return ret, splitnewlines(output)
281
283
282 def run_one(test, skips, fails):
284 def run_one(test, skips, fails):
283 '''tristate output:
285 '''tristate output:
284 None -> skipped
286 None -> skipped
285 True -> passed
287 True -> passed
286 False -> failed'''
288 False -> failed'''
287
289
288 def skip(msg):
290 def skip(msg):
289 if not verbose:
291 if not verbose:
290 skips.append((test, msg))
292 skips.append((test, msg))
291 else:
293 else:
292 print "\nSkipping %s: %s" % (test, msg)
294 print "\nSkipping %s: %s" % (test, msg)
293 return None
295 return None
294
296
295 def fail(msg):
297 def fail(msg):
296 fails.append((test, msg))
298 fails.append((test, msg))
297 print "\nERROR: %s %s" % (test, msg)
299 print "\nERROR: %s %s" % (test, msg)
298 return None
300 return None
299
301
300 vlog("# Test", test)
302 vlog("# Test", test)
301
303
302 # create a fresh hgrc
304 # create a fresh hgrc
303 hgrc = file(HGRCPATH, 'w+')
305 hgrc = file(HGRCPATH, 'w+')
304 hgrc.write('[ui]\n')
306 hgrc.write('[ui]\n')
305 hgrc.write('slash = True\n')
307 hgrc.write('slash = True\n')
306 hgrc.write('[defaults]\n')
308 hgrc.write('[defaults]\n')
307 hgrc.write('backout = -d "0 0"\n')
309 hgrc.write('backout = -d "0 0"\n')
308 hgrc.write('commit = -d "0 0"\n')
310 hgrc.write('commit = -d "0 0"\n')
309 hgrc.write('debugrawcommit = -d "0 0"\n')
311 hgrc.write('debugrawcommit = -d "0 0"\n')
310 hgrc.write('tag = -d "0 0"\n')
312 hgrc.write('tag = -d "0 0"\n')
311 hgrc.close()
313 hgrc.close()
312
314
313 err = os.path.join(TESTDIR, test+".err")
315 err = os.path.join(TESTDIR, test+".err")
314 ref = os.path.join(TESTDIR, test+".out")
316 ref = os.path.join(TESTDIR, test+".out")
315 testpath = os.path.join(TESTDIR, test)
317 testpath = os.path.join(TESTDIR, test)
316
318
317 if os.path.exists(err):
319 if os.path.exists(err):
318 os.remove(err) # Remove any previous output files
320 os.remove(err) # Remove any previous output files
319
321
320 # Make a tmp subdirectory to work in
322 # Make a tmp subdirectory to work in
321 tmpd = os.path.join(HGTMP, test)
323 tmpd = os.path.join(HGTMP, test)
322 os.mkdir(tmpd)
324 os.mkdir(tmpd)
323 os.chdir(tmpd)
325 os.chdir(tmpd)
324
326
325 try:
327 try:
326 tf = open(testpath)
328 tf = open(testpath)
327 firstline = tf.readline().rstrip()
329 firstline = tf.readline().rstrip()
328 tf.close()
330 tf.close()
329 except:
331 except:
330 firstline = ''
332 firstline = ''
331 lctest = test.lower()
333 lctest = test.lower()
332
334
333 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
335 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
334 cmd = '%s "%s"' % (python, testpath)
336 cmd = '%s "%s"' % (python, testpath)
335 elif lctest.endswith('.bat'):
337 elif lctest.endswith('.bat'):
336 # do not run batch scripts on non-windows
338 # do not run batch scripts on non-windows
337 if os.name != 'nt':
339 if os.name != 'nt':
338 return skip("batch script")
340 return skip("batch script")
339 # To reliably get the error code from batch files on WinXP,
341 # To reliably get the error code from batch files on WinXP,
340 # the "cmd /c call" prefix is needed. Grrr
342 # the "cmd /c call" prefix is needed. Grrr
341 cmd = 'cmd /c call "%s"' % testpath
343 cmd = 'cmd /c call "%s"' % testpath
342 else:
344 else:
343 # do not run shell scripts on windows
345 # do not run shell scripts on windows
344 if os.name == 'nt':
346 if os.name == 'nt':
345 return skip("shell script")
347 return skip("shell script")
346 # do not try to run non-executable programs
348 # do not try to run non-executable programs
347 if not os.access(testpath, os.X_OK):
349 if not os.access(testpath, os.X_OK):
348 return skip("not executable")
350 return skip("not executable")
349 cmd = '"%s"' % testpath
351 cmd = '"%s"' % testpath
350
352
351 if options.timeout > 0:
353 if options.timeout > 0:
352 signal.alarm(options.timeout)
354 signal.alarm(options.timeout)
353
355
354 vlog("# Running", cmd)
356 vlog("# Running", cmd)
355 ret, out = run(cmd)
357 ret, out = run(cmd)
356 vlog("# Ret was:", ret)
358 vlog("# Ret was:", ret)
357
359
358 if options.timeout > 0:
360 if options.timeout > 0:
359 signal.alarm(0)
361 signal.alarm(0)
360
362
361 skipped = (ret == SKIPPED_STATUS)
363 skipped = (ret == SKIPPED_STATUS)
362 # If reference output file exists, check test output against it
364 # If reference output file exists, check test output against it
363 if os.path.exists(ref):
365 if os.path.exists(ref):
364 f = open(ref, "r")
366 f = open(ref, "r")
365 ref_out = splitnewlines(f.read())
367 ref_out = splitnewlines(f.read())
366 f.close()
368 f.close()
367 else:
369 else:
368 ref_out = []
370 ref_out = []
369 if skipped:
371 if skipped:
370 missing = extract_missing_features(out)
372 missing = extract_missing_features(out)
371 if not missing:
373 if not missing:
372 missing = ['irrelevant']
374 missing = ['irrelevant']
373 skip(missing[-1])
375 skip(missing[-1])
374 elif out != ref_out:
376 elif out != ref_out:
375 if ret:
377 if ret:
376 fail("output changed and returned error code %d" % ret)
378 fail("output changed and returned error code %d" % ret)
377 else:
379 else:
378 fail("output changed")
380 fail("output changed")
379 show_diff(ref_out, out)
381 show_diff(ref_out, out)
380 ret = 1
382 ret = 1
381 elif ret:
383 elif ret:
382 fail("returned error code %d" % ret)
384 fail("returned error code %d" % ret)
383
385
384 if not verbose:
386 if not verbose:
385 sys.stdout.write(skipped and 's' or '.')
387 sys.stdout.write(skipped and 's' or '.')
386 sys.stdout.flush()
388 sys.stdout.flush()
387
389
388 if ret != 0 and not skipped:
390 if ret != 0 and not skipped:
389 # Save errors to a file for diagnosis
391 # Save errors to a file for diagnosis
390 f = open(err, "wb")
392 f = open(err, "wb")
391 for line in out:
393 for line in out:
392 f.write(line)
394 f.write(line)
393 f.close()
395 f.close()
394
396
395 # Kill off any leftover daemon processes
397 # Kill off any leftover daemon processes
396 try:
398 try:
397 fp = file(DAEMON_PIDS)
399 fp = file(DAEMON_PIDS)
398 for line in fp:
400 for line in fp:
399 try:
401 try:
400 pid = int(line)
402 pid = int(line)
401 except ValueError:
403 except ValueError:
402 continue
404 continue
403 try:
405 try:
404 os.kill(pid, 0)
406 os.kill(pid, 0)
405 vlog('# Killing daemon process %d' % pid)
407 vlog('# Killing daemon process %d' % pid)
406 os.kill(pid, signal.SIGTERM)
408 os.kill(pid, signal.SIGTERM)
407 time.sleep(0.25)
409 time.sleep(0.25)
408 os.kill(pid, 0)
410 os.kill(pid, 0)
409 vlog('# Daemon process %d is stuck - really killing it' % pid)
411 vlog('# Daemon process %d is stuck - really killing it' % pid)
410 os.kill(pid, signal.SIGKILL)
412 os.kill(pid, signal.SIGKILL)
411 except OSError, err:
413 except OSError, err:
412 if err.errno != errno.ESRCH:
414 if err.errno != errno.ESRCH:
413 raise
415 raise
414 fp.close()
416 fp.close()
415 os.unlink(DAEMON_PIDS)
417 os.unlink(DAEMON_PIDS)
416 except IOError:
418 except IOError:
417 pass
419 pass
418
420
419 os.chdir(TESTDIR)
421 os.chdir(TESTDIR)
420 if not options.keep_tmpdir:
422 if not options.keep_tmpdir:
421 shutil.rmtree(tmpd, True)
423 shutil.rmtree(tmpd, True)
422 if skipped:
424 if skipped:
423 return None
425 return None
424 return ret == 0
426 return ret == 0
425
427
426 if not options.child:
428 if not options.child:
427 os.umask(022)
429 os.umask(022)
428
430
429 check_required_tools()
431 check_required_tools()
430
432
431 # Reset some environment variables to well-known values so that
433 # Reset some environment variables to well-known values so that
432 # the tests produce repeatable output.
434 # the tests produce repeatable output.
433 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
435 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
434 os.environ['TZ'] = 'GMT'
436 os.environ['TZ'] = 'GMT'
435 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
437 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
436
438
437 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
439 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
438 HGTMP = os.environ['HGTMP'] = tempfile.mkdtemp('', 'hgtests.', options.tmpdir)
440 HGTMP = os.environ['HGTMP'] = tempfile.mkdtemp('', 'hgtests.', options.tmpdir)
439 DAEMON_PIDS = None
441 DAEMON_PIDS = None
440 HGRCPATH = None
442 HGRCPATH = None
441
443
442 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
444 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
443 os.environ["HGMERGE"] = "internal:merge"
445 os.environ["HGMERGE"] = "internal:merge"
444 os.environ["HGUSER"] = "test"
446 os.environ["HGUSER"] = "test"
445 os.environ["HGENCODING"] = "ascii"
447 os.environ["HGENCODING"] = "ascii"
446 os.environ["HGENCODINGMODE"] = "strict"
448 os.environ["HGENCODINGMODE"] = "strict"
447 os.environ["HGPORT"] = str(options.port)
449 os.environ["HGPORT"] = str(options.port)
448 os.environ["HGPORT1"] = str(options.port + 1)
450 os.environ["HGPORT1"] = str(options.port + 1)
449 os.environ["HGPORT2"] = str(options.port + 2)
451 os.environ["HGPORT2"] = str(options.port + 2)
450
452
451 if options.with_hg:
453 if options.with_hg:
452 INST = options.with_hg
454 INST = options.with_hg
453 else:
455 else:
454 INST = os.path.join(HGTMP, "install")
456 INST = os.path.join(HGTMP, "install")
455 BINDIR = os.path.join(INST, "bin")
457 BINDIR = os.path.join(INST, "bin")
456 PYTHONDIR = os.path.join(INST, "lib", "python")
458 PYTHONDIR = os.path.join(INST, "lib", "python")
457 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
459 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
458
460
461 def _hgpath():
462 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
463 hgpath = os.popen(cmd % python)
464 path = hgpath.read().strip()
465 hgpath.close()
466 return path
467
468 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
469 hgpkg = None
470
459 def run_children(tests):
471 def run_children(tests):
460 if not options.with_hg:
472 if not options.with_hg:
461 install_hg()
473 install_hg()
462
474
463 optcopy = dict(options.__dict__)
475 optcopy = dict(options.__dict__)
464 optcopy['jobs'] = 1
476 optcopy['jobs'] = 1
465 optcopy['with_hg'] = INST
477 optcopy['with_hg'] = INST
466 opts = []
478 opts = []
467 for opt, value in optcopy.iteritems():
479 for opt, value in optcopy.iteritems():
468 name = '--' + opt.replace('_', '-')
480 name = '--' + opt.replace('_', '-')
469 if value is True:
481 if value is True:
470 opts.append(name)
482 opts.append(name)
471 elif value is not None:
483 elif value is not None:
472 opts.append(name + '=' + str(value))
484 opts.append(name + '=' + str(value))
473
485
474 tests.reverse()
486 tests.reverse()
475 jobs = [[] for j in xrange(options.jobs)]
487 jobs = [[] for j in xrange(options.jobs)]
476 while tests:
488 while tests:
477 for j in xrange(options.jobs):
489 for j in xrange(options.jobs):
478 if not tests: break
490 if not tests: break
479 jobs[j].append(tests.pop())
491 jobs[j].append(tests.pop())
480 fps = {}
492 fps = {}
481 for j in xrange(len(jobs)):
493 for j in xrange(len(jobs)):
482 job = jobs[j]
494 job = jobs[j]
483 if not job:
495 if not job:
484 continue
496 continue
485 rfd, wfd = os.pipe()
497 rfd, wfd = os.pipe()
486 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
498 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
487 cmdline = [python, sys.argv[0]] + opts + childopts + job
499 cmdline = [python, sys.argv[0]] + opts + childopts + job
488 vlog(' '.join(cmdline))
500 vlog(' '.join(cmdline))
489 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
501 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
490 os.close(wfd)
502 os.close(wfd)
491 failures = 0
503 failures = 0
492 tested, skipped, failed = 0, 0, 0
504 tested, skipped, failed = 0, 0, 0
493 skips = []
505 skips = []
494 fails = []
506 fails = []
495 while fps:
507 while fps:
496 pid, status = os.wait()
508 pid, status = os.wait()
497 fp = fps.pop(pid)
509 fp = fps.pop(pid)
498 l = fp.read().splitlines()
510 l = fp.read().splitlines()
499 test, skip, fail = map(int, l[:3])
511 test, skip, fail = map(int, l[:3])
500 split = -fail or len(l)
512 split = -fail or len(l)
501 for s in l[3:split]:
513 for s in l[3:split]:
502 skips.append(s.split(" ", 1))
514 skips.append(s.split(" ", 1))
503 for s in l[split:]:
515 for s in l[split:]:
504 fails.append(s.split(" ", 1))
516 fails.append(s.split(" ", 1))
505 tested += test
517 tested += test
506 skipped += skip
518 skipped += skip
507 failed += fail
519 failed += fail
508 vlog('pid %d exited, status %d' % (pid, status))
520 vlog('pid %d exited, status %d' % (pid, status))
509 failures |= status
521 failures |= status
510 print
522 print
511 for s in skips:
523 for s in skips:
512 print "Skipped %s: %s" % (s[0], s[1])
524 print "Skipped %s: %s" % (s[0], s[1])
513 for s in fails:
525 for s in fails:
514 print "Failed %s: %s" % (s[0], s[1])
526 print "Failed %s: %s" % (s[0], s[1])
527
528 if hgpkg != expecthg:
529 print '# Tested unexpected mercurial: %s' % hgpkg
515 print "# Ran %d tests, %d skipped, %d failed." % (
530 print "# Ran %d tests, %d skipped, %d failed." % (
516 tested, skipped, failed)
531 tested, skipped, failed)
517 sys.exit(failures != 0)
532 sys.exit(failures != 0)
518
533
519 def run_tests(tests):
534 def run_tests(tests):
520 global DAEMON_PIDS, HGRCPATH
535 global DAEMON_PIDS, HGRCPATH
521 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
536 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
522 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
537 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
523
538
524 try:
539 try:
525 if not options.with_hg:
540 if not options.with_hg:
526 install_hg()
541 install_hg()
527
542
543 if hgpkg != expecthg:
544 print '# Testing unexpected mercurial: %s' % hgpkg
545
528 if options.timeout > 0:
546 if options.timeout > 0:
529 try:
547 try:
530 signal.signal(signal.SIGALRM, alarmed)
548 signal.signal(signal.SIGALRM, alarmed)
531 vlog('# Running tests with %d-second timeout' %
549 vlog('# Running tests with %d-second timeout' %
532 options.timeout)
550 options.timeout)
533 except AttributeError:
551 except AttributeError:
534 print 'WARNING: cannot run tests with timeouts'
552 print 'WARNING: cannot run tests with timeouts'
535 options.timeout = 0
553 options.timeout = 0
536
554
537 tested = 0
555 tested = 0
538 failed = 0
556 failed = 0
539 skipped = 0
557 skipped = 0
540
558
541 if options.restart:
559 if options.restart:
542 orig = list(tests)
560 orig = list(tests)
543 while tests:
561 while tests:
544 if os.path.exists(tests[0] + ".err"):
562 if os.path.exists(tests[0] + ".err"):
545 break
563 break
546 tests.pop(0)
564 tests.pop(0)
547 if not tests:
565 if not tests:
548 print "running all tests"
566 print "running all tests"
549 tests = orig
567 tests = orig
550
568
551 skips = []
569 skips = []
552 fails = []
570 fails = []
553 for test in tests:
571 for test in tests:
554 if options.retest and not os.path.exists(test + ".err"):
572 if options.retest and not os.path.exists(test + ".err"):
555 skipped += 1
573 skipped += 1
556 continue
574 continue
557 ret = run_one(test, skips, fails)
575 ret = run_one(test, skips, fails)
558 if ret is None:
576 if ret is None:
559 skipped += 1
577 skipped += 1
560 elif not ret:
578 elif not ret:
561 if options.interactive:
579 if options.interactive:
562 print "Accept this change? [n] ",
580 print "Accept this change? [n] ",
563 answer = sys.stdin.readline().strip()
581 answer = sys.stdin.readline().strip()
564 if answer.lower() in "y yes".split():
582 if answer.lower() in "y yes".split():
565 rename(test + ".err", test + ".out")
583 rename(test + ".err", test + ".out")
566 tested += 1
584 tested += 1
567 fails.pop()
585 fails.pop()
568 continue
586 continue
569 failed += 1
587 failed += 1
570 if options.first:
588 if options.first:
571 break
589 break
572 tested += 1
590 tested += 1
573
591
574 if options.child:
592 if options.child:
575 fp = os.fdopen(options.child, 'w')
593 fp = os.fdopen(options.child, 'w')
576 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
594 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
577 for s in skips:
595 for s in skips:
578 fp.write("%s %s\n" % s)
596 fp.write("%s %s\n" % s)
579 for s in fails:
597 for s in fails:
580 fp.write("%s %s\n" % s)
598 fp.write("%s %s\n" % s)
581 fp.close()
599 fp.close()
582 else:
600 else:
583 print
601 print
584 for s in skips:
602 for s in skips:
585 print "Skipped %s: %s" % s
603 print "Skipped %s: %s" % s
586 for s in fails:
604 for s in fails:
587 print "Failed %s: %s" % s
605 print "Failed %s: %s" % s
606 if hgpkg != expecthg:
607 print '# Tested unexpected mercurial: %s' % hgpkg
588 print "# Ran %d tests, %d skipped, %d failed." % (
608 print "# Ran %d tests, %d skipped, %d failed." % (
589 tested, skipped, failed)
609 tested, skipped, failed)
590
610
591 if coverage:
611 if coverage:
592 output_coverage()
612 output_coverage()
593 except KeyboardInterrupt:
613 except KeyboardInterrupt:
594 failed = True
614 failed = True
595 print "\ninterrupted!"
615 print "\ninterrupted!"
596
616
597 if failed:
617 if failed:
598 sys.exit(1)
618 sys.exit(1)
599
619
600 if len(args) == 0:
620 if len(args) == 0:
601 args = os.listdir(".")
621 args = os.listdir(".")
602 args.sort()
622 args.sort()
603
623
604 tests = []
624 tests = []
605 for test in args:
625 for test in args:
606 if (test.startswith("test-") and '~' not in test and
626 if (test.startswith("test-") and '~' not in test and
607 ('.' not in test or test.endswith('.py') or
627 ('.' not in test or test.endswith('.py') or
608 test.endswith('.bat'))):
628 test.endswith('.bat'))):
609 tests.append(test)
629 tests.append(test)
610
630
611 vlog("# Using TESTDIR", TESTDIR)
631 vlog("# Using TESTDIR", TESTDIR)
612 vlog("# Using HGTMP", HGTMP)
632 vlog("# Using HGTMP", HGTMP)
613
633
614 try:
634 try:
615 if len(tests) > 1 and options.jobs > 1:
635 if len(tests) > 1 and options.jobs > 1:
616 run_children(tests)
636 run_children(tests)
617 else:
637 else:
618 run_tests(tests)
638 run_tests(tests)
619 finally:
639 finally:
620 cleanup_exit()
640 cleanup_exit()
General Comments 0
You need to be logged in to leave comments. Login now