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