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