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