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