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