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