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