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