##// END OF EJS Templates
run-tests.py: don't print a dot for skipped tests
Alexis S. L. Carvalho -
r5518:d0a022d5 default
parent child Browse files
Show More
@@ -1,578 +1,576
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, skips):
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):
266 def skip(msg):
267 if not verbose:
267 if not verbose:
268 skips.append((test, msg))
268 skips.append((test, msg))
269 sys.stdout.write('s')
270 sys.stdout.flush()
271 else:
269 else:
272 print "\nSkipping %s: %s" % (test, msg)
270 print "\nSkipping %s: %s" % (test, msg)
273 return None
271 return None
274
272
275 vlog("# Test", test)
273 vlog("# Test", test)
276
274
277 # create a fresh hgrc
275 # create a fresh hgrc
278 hgrc = file(HGRCPATH, 'w+')
276 hgrc = file(HGRCPATH, 'w+')
279 hgrc.write('[ui]\n')
277 hgrc.write('[ui]\n')
280 hgrc.write('slash = True\n')
278 hgrc.write('slash = True\n')
281 hgrc.close()
279 hgrc.close()
282
280
283 err = os.path.join(TESTDIR, test+".err")
281 err = os.path.join(TESTDIR, test+".err")
284 ref = os.path.join(TESTDIR, test+".out")
282 ref = os.path.join(TESTDIR, test+".out")
285 testpath = os.path.join(TESTDIR, test)
283 testpath = os.path.join(TESTDIR, test)
286
284
287 if os.path.exists(err):
285 if os.path.exists(err):
288 os.remove(err) # Remove any previous output files
286 os.remove(err) # Remove any previous output files
289
287
290 # Make a tmp subdirectory to work in
288 # Make a tmp subdirectory to work in
291 tmpd = os.path.join(HGTMP, test)
289 tmpd = os.path.join(HGTMP, test)
292 os.mkdir(tmpd)
290 os.mkdir(tmpd)
293 os.chdir(tmpd)
291 os.chdir(tmpd)
294
292
295 try:
293 try:
296 tf = open(testpath)
294 tf = open(testpath)
297 firstline = tf.readline().rstrip()
295 firstline = tf.readline().rstrip()
298 tf.close()
296 tf.close()
299 except:
297 except:
300 firstline = ''
298 firstline = ''
301 lctest = test.lower()
299 lctest = test.lower()
302
300
303 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
301 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
304 cmd = '%s "%s"' % (python, testpath)
302 cmd = '%s "%s"' % (python, testpath)
305 elif lctest.endswith('.bat'):
303 elif lctest.endswith('.bat'):
306 # do not run batch scripts on non-windows
304 # do not run batch scripts on non-windows
307 if os.name != 'nt':
305 if os.name != 'nt':
308 return skip("batch script")
306 return skip("batch script")
309 # To reliably get the error code from batch files on WinXP,
307 # To reliably get the error code from batch files on WinXP,
310 # the "cmd /c call" prefix is needed. Grrr
308 # the "cmd /c call" prefix is needed. Grrr
311 cmd = 'cmd /c call "%s"' % testpath
309 cmd = 'cmd /c call "%s"' % testpath
312 else:
310 else:
313 # do not run shell scripts on windows
311 # do not run shell scripts on windows
314 if os.name == 'nt':
312 if os.name == 'nt':
315 return skip("shell script")
313 return skip("shell script")
316 # do not try to run non-executable programs
314 # do not try to run non-executable programs
317 if not os.access(testpath, os.X_OK):
315 if not os.access(testpath, os.X_OK):
318 return skip("not executable")
316 return skip("not executable")
319 cmd = '"%s"' % testpath
317 cmd = '"%s"' % testpath
320
318
321 if options.timeout > 0:
319 if options.timeout > 0:
322 signal.alarm(options.timeout)
320 signal.alarm(options.timeout)
323
321
324 vlog("# Running", cmd)
322 vlog("# Running", cmd)
325 ret, out = run(cmd)
323 ret, out = run(cmd)
326 vlog("# Ret was:", ret)
324 vlog("# Ret was:", ret)
327
325
328 if options.timeout > 0:
326 if options.timeout > 0:
329 signal.alarm(0)
327 signal.alarm(0)
330
328
331 skipped = (ret == SKIPPED_STATUS)
329 skipped = (ret == SKIPPED_STATUS)
332 diffret = 0
330 diffret = 0
333 # If reference output file exists, check test output against it
331 # If reference output file exists, check test output against it
334 if os.path.exists(ref):
332 if os.path.exists(ref):
335 f = open(ref, "r")
333 f = open(ref, "r")
336 ref_out = splitnewlines(f.read())
334 ref_out = splitnewlines(f.read())
337 f.close()
335 f.close()
338 else:
336 else:
339 ref_out = []
337 ref_out = []
340 if not skipped and out != ref_out:
338 if not skipped and out != ref_out:
341 diffret = 1
339 diffret = 1
342 print "\nERROR: %s output changed" % (test)
340 print "\nERROR: %s output changed" % (test)
343 show_diff(ref_out, out)
341 show_diff(ref_out, out)
344 if skipped:
342 if skipped:
345 missing = extract_missing_features(out)
343 missing = extract_missing_features(out)
346 if not missing:
344 if not missing:
347 missing = ['irrelevant']
345 missing = ['irrelevant']
348 skip(missing[-1])
346 skip(missing[-1])
349 elif ret:
347 elif ret:
350 print "\nERROR: %s failed with error code %d" % (test, ret)
348 print "\nERROR: %s failed with error code %d" % (test, ret)
351 elif diffret:
349 elif diffret:
352 ret = diffret
350 ret = diffret
353
351
354 if not verbose:
352 if not verbose:
355 sys.stdout.write('.')
353 sys.stdout.write(skipped and 's' or '.')
356 sys.stdout.flush()
354 sys.stdout.flush()
357
355
358 if ret != 0 and not skipped:
356 if ret != 0 and not skipped:
359 # Save errors to a file for diagnosis
357 # Save errors to a file for diagnosis
360 f = open(err, "wb")
358 f = open(err, "wb")
361 for line in out:
359 for line in out:
362 f.write(line)
360 f.write(line)
363 f.close()
361 f.close()
364
362
365 # Kill off any leftover daemon processes
363 # Kill off any leftover daemon processes
366 try:
364 try:
367 fp = file(DAEMON_PIDS)
365 fp = file(DAEMON_PIDS)
368 for line in fp:
366 for line in fp:
369 try:
367 try:
370 pid = int(line)
368 pid = int(line)
371 except ValueError:
369 except ValueError:
372 continue
370 continue
373 try:
371 try:
374 os.kill(pid, 0)
372 os.kill(pid, 0)
375 vlog('# Killing daemon process %d' % pid)
373 vlog('# Killing daemon process %d' % pid)
376 os.kill(pid, signal.SIGTERM)
374 os.kill(pid, signal.SIGTERM)
377 time.sleep(0.25)
375 time.sleep(0.25)
378 os.kill(pid, 0)
376 os.kill(pid, 0)
379 vlog('# Daemon process %d is stuck - really killing it' % pid)
377 vlog('# Daemon process %d is stuck - really killing it' % pid)
380 os.kill(pid, signal.SIGKILL)
378 os.kill(pid, signal.SIGKILL)
381 except OSError, err:
379 except OSError, err:
382 if err.errno != errno.ESRCH:
380 if err.errno != errno.ESRCH:
383 raise
381 raise
384 fp.close()
382 fp.close()
385 os.unlink(DAEMON_PIDS)
383 os.unlink(DAEMON_PIDS)
386 except IOError:
384 except IOError:
387 pass
385 pass
388
386
389 os.chdir(TESTDIR)
387 os.chdir(TESTDIR)
390 shutil.rmtree(tmpd, True)
388 shutil.rmtree(tmpd, True)
391 if skipped:
389 if skipped:
392 return None
390 return None
393 return ret == 0
391 return ret == 0
394
392
395 if not options.child:
393 if not options.child:
396 os.umask(022)
394 os.umask(022)
397
395
398 check_required_tools()
396 check_required_tools()
399
397
400 # Reset some environment variables to well-known values so that
398 # Reset some environment variables to well-known values so that
401 # the tests produce repeatable output.
399 # the tests produce repeatable output.
402 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
400 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
403 os.environ['TZ'] = 'GMT'
401 os.environ['TZ'] = 'GMT'
404
402
405 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
403 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
406 HGTMP = os.environ['HGTMP'] = tempfile.mkdtemp('', 'hgtests.', options.tmpdir)
404 HGTMP = os.environ['HGTMP'] = tempfile.mkdtemp('', 'hgtests.', options.tmpdir)
407 DAEMON_PIDS = None
405 DAEMON_PIDS = None
408 HGRCPATH = None
406 HGRCPATH = None
409
407
410 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
408 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
411 os.environ["HGMERGE"] = ('python "%s" -L my -L other'
409 os.environ["HGMERGE"] = ('python "%s" -L my -L other'
412 % os.path.join(TESTDIR, os.path.pardir,
410 % os.path.join(TESTDIR, os.path.pardir,
413 'contrib', 'simplemerge'))
411 'contrib', 'simplemerge'))
414 os.environ["HGUSER"] = "test"
412 os.environ["HGUSER"] = "test"
415 os.environ["HGENCODING"] = "ascii"
413 os.environ["HGENCODING"] = "ascii"
416 os.environ["HGENCODINGMODE"] = "strict"
414 os.environ["HGENCODINGMODE"] = "strict"
417 os.environ["HGPORT"] = str(options.port)
415 os.environ["HGPORT"] = str(options.port)
418 os.environ["HGPORT1"] = str(options.port + 1)
416 os.environ["HGPORT1"] = str(options.port + 1)
419 os.environ["HGPORT2"] = str(options.port + 2)
417 os.environ["HGPORT2"] = str(options.port + 2)
420
418
421 if options.with_hg:
419 if options.with_hg:
422 INST = options.with_hg
420 INST = options.with_hg
423 else:
421 else:
424 INST = os.path.join(HGTMP, "install")
422 INST = os.path.join(HGTMP, "install")
425 BINDIR = os.path.join(INST, "bin")
423 BINDIR = os.path.join(INST, "bin")
426 PYTHONDIR = os.path.join(INST, "lib", "python")
424 PYTHONDIR = os.path.join(INST, "lib", "python")
427 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
425 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
428
426
429 def run_children(tests):
427 def run_children(tests):
430 if not options.with_hg:
428 if not options.with_hg:
431 install_hg()
429 install_hg()
432
430
433 optcopy = dict(options.__dict__)
431 optcopy = dict(options.__dict__)
434 optcopy['jobs'] = 1
432 optcopy['jobs'] = 1
435 optcopy['with_hg'] = INST
433 optcopy['with_hg'] = INST
436 opts = []
434 opts = []
437 for opt, value in optcopy.iteritems():
435 for opt, value in optcopy.iteritems():
438 name = '--' + opt.replace('_', '-')
436 name = '--' + opt.replace('_', '-')
439 if value is True:
437 if value is True:
440 opts.append(name)
438 opts.append(name)
441 elif value is not None:
439 elif value is not None:
442 opts.append(name + '=' + str(value))
440 opts.append(name + '=' + str(value))
443
441
444 tests.reverse()
442 tests.reverse()
445 jobs = [[] for j in xrange(options.jobs)]
443 jobs = [[] for j in xrange(options.jobs)]
446 while tests:
444 while tests:
447 for j in xrange(options.jobs):
445 for j in xrange(options.jobs):
448 if not tests: break
446 if not tests: break
449 jobs[j].append(tests.pop())
447 jobs[j].append(tests.pop())
450 fps = {}
448 fps = {}
451 for j in xrange(len(jobs)):
449 for j in xrange(len(jobs)):
452 job = jobs[j]
450 job = jobs[j]
453 if not job:
451 if not job:
454 continue
452 continue
455 rfd, wfd = os.pipe()
453 rfd, wfd = os.pipe()
456 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
454 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
457 cmdline = [python, sys.argv[0]] + opts + childopts + job
455 cmdline = [python, sys.argv[0]] + opts + childopts + job
458 vlog(' '.join(cmdline))
456 vlog(' '.join(cmdline))
459 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
457 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
460 os.close(wfd)
458 os.close(wfd)
461 failures = 0
459 failures = 0
462 tested, skipped, failed = 0, 0, 0
460 tested, skipped, failed = 0, 0, 0
463 skips = []
461 skips = []
464 while fps:
462 while fps:
465 pid, status = os.wait()
463 pid, status = os.wait()
466 fp = fps.pop(pid)
464 fp = fps.pop(pid)
467 l = fp.read().splitlines()
465 l = fp.read().splitlines()
468 test, skip, fail = map(int, l[:3])
466 test, skip, fail = map(int, l[:3])
469 for s in l[3:]:
467 for s in l[3:]:
470 skips.append(s.split(" ", 1))
468 skips.append(s.split(" ", 1))
471 tested += test
469 tested += test
472 skipped += skip
470 skipped += skip
473 failed += fail
471 failed += fail
474 vlog('pid %d exited, status %d' % (pid, status))
472 vlog('pid %d exited, status %d' % (pid, status))
475 failures |= status
473 failures |= status
476 print
474 print
477 for s in skips:
475 for s in skips:
478 print "Skipped %s: %s" % (s[0], s[1])
476 print "Skipped %s: %s" % (s[0], s[1])
479 print "# Ran %d tests, %d skipped, %d failed." % (
477 print "# Ran %d tests, %d skipped, %d failed." % (
480 tested, skipped, failed)
478 tested, skipped, failed)
481 sys.exit(failures != 0)
479 sys.exit(failures != 0)
482
480
483 def run_tests(tests):
481 def run_tests(tests):
484 global DAEMON_PIDS, HGRCPATH
482 global DAEMON_PIDS, HGRCPATH
485 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
483 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
486 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
484 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
487
485
488 try:
486 try:
489 if not options.with_hg:
487 if not options.with_hg:
490 install_hg()
488 install_hg()
491
489
492 if options.timeout > 0:
490 if options.timeout > 0:
493 try:
491 try:
494 signal.signal(signal.SIGALRM, alarmed)
492 signal.signal(signal.SIGALRM, alarmed)
495 vlog('# Running tests with %d-second timeout' %
493 vlog('# Running tests with %d-second timeout' %
496 options.timeout)
494 options.timeout)
497 except AttributeError:
495 except AttributeError:
498 print 'WARNING: cannot run tests with timeouts'
496 print 'WARNING: cannot run tests with timeouts'
499 options.timeout = 0
497 options.timeout = 0
500
498
501 tested = 0
499 tested = 0
502 failed = 0
500 failed = 0
503 skipped = 0
501 skipped = 0
504
502
505 if options.restart:
503 if options.restart:
506 orig = list(tests)
504 orig = list(tests)
507 while tests:
505 while tests:
508 if os.path.exists(tests[0] + ".err"):
506 if os.path.exists(tests[0] + ".err"):
509 break
507 break
510 tests.pop(0)
508 tests.pop(0)
511 if not tests:
509 if not tests:
512 print "running all tests"
510 print "running all tests"
513 tests = orig
511 tests = orig
514
512
515 skips = []
513 skips = []
516 for test in tests:
514 for test in tests:
517 if options.retest and not os.path.exists(test + ".err"):
515 if options.retest and not os.path.exists(test + ".err"):
518 skipped += 1
516 skipped += 1
519 continue
517 continue
520 ret = run_one(test, skips)
518 ret = run_one(test, skips)
521 if ret is None:
519 if ret is None:
522 skipped += 1
520 skipped += 1
523 elif not ret:
521 elif not ret:
524 if options.interactive:
522 if options.interactive:
525 print "Accept this change? [n] ",
523 print "Accept this change? [n] ",
526 answer = sys.stdin.readline().strip()
524 answer = sys.stdin.readline().strip()
527 if answer.lower() in "y yes".split():
525 if answer.lower() in "y yes".split():
528 os.rename(test + ".err", test + ".out")
526 os.rename(test + ".err", test + ".out")
529 tested += 1
527 tested += 1
530 continue
528 continue
531 failed += 1
529 failed += 1
532 if options.first:
530 if options.first:
533 break
531 break
534 tested += 1
532 tested += 1
535
533
536 if options.child:
534 if options.child:
537 fp = os.fdopen(options.child, 'w')
535 fp = os.fdopen(options.child, 'w')
538 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
536 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
539 for s in skips:
537 for s in skips:
540 fp.write("%s %s\n" % s)
538 fp.write("%s %s\n" % s)
541 fp.close()
539 fp.close()
542 else:
540 else:
543 print
541 print
544 for s in skips:
542 for s in skips:
545 print "Skipped %s: %s" % s
543 print "Skipped %s: %s" % s
546 print "# Ran %d tests, %d skipped, %d failed." % (
544 print "# Ran %d tests, %d skipped, %d failed." % (
547 tested, skipped, failed)
545 tested, skipped, failed)
548
546
549 if coverage:
547 if coverage:
550 output_coverage()
548 output_coverage()
551 except KeyboardInterrupt:
549 except KeyboardInterrupt:
552 failed = True
550 failed = True
553 print "\ninterrupted!"
551 print "\ninterrupted!"
554
552
555 if failed:
553 if failed:
556 sys.exit(1)
554 sys.exit(1)
557
555
558 if len(args) == 0:
556 if len(args) == 0:
559 args = os.listdir(".")
557 args = os.listdir(".")
560 args.sort()
558 args.sort()
561
559
562 tests = []
560 tests = []
563 for test in args:
561 for test in args:
564 if (test.startswith("test-") and '~' not in test and
562 if (test.startswith("test-") and '~' not in test and
565 ('.' not in test or test.endswith('.py') or
563 ('.' not in test or test.endswith('.py') or
566 test.endswith('.bat'))):
564 test.endswith('.bat'))):
567 tests.append(test)
565 tests.append(test)
568
566
569 vlog("# Using TESTDIR", TESTDIR)
567 vlog("# Using TESTDIR", TESTDIR)
570 vlog("# Using HGTMP", HGTMP)
568 vlog("# Using HGTMP", HGTMP)
571
569
572 try:
570 try:
573 if len(tests) > 1 and options.jobs > 1:
571 if len(tests) > 1 and options.jobs > 1:
574 run_children(tests)
572 run_children(tests)
575 else:
573 else:
576 run_tests(tests)
574 run_tests(tests)
577 finally:
575 finally:
578 cleanup_exit()
576 cleanup_exit()
General Comments 0
You need to be logged in to leave comments. Login now