##// END OF EJS Templates
run-tests: expand --tmpdir and create it if needed
Nicolas Dumazet -
r9394:31203db1 default
parent child Browse files
Show More
@@ -1,824 +1,832 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 of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
8 # GNU General Public License version 2, incorporated herein by reference.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install
34 # 8) parallel, coverage, local install
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 #
36 #
37 # (You could use any subset of the tests: test-s* happens to match
37 # (You could use any subset of the tests: test-s* happens to match
38 # enough that it's worth doing parallel runs, few enough that it
38 # enough that it's worth doing parallel runs, few enough that it
39 # completes fairly quickly, includes both shell and Python scripts, and
39 # completes fairly quickly, includes both shell and Python scripts, and
40 # includes some scripts that run daemon processes.)
40 # includes some scripts that run daemon processes.)
41
41
42 import difflib
42 import difflib
43 import errno
43 import errno
44 import optparse
44 import optparse
45 import os
45 import os
46 import subprocess
46 import subprocess
47 import shutil
47 import shutil
48 import signal
48 import signal
49 import sys
49 import sys
50 import tempfile
50 import tempfile
51 import time
51 import time
52
52
53 closefds = os.name == 'posix'
53 closefds = os.name == 'posix'
54 def Popen4(cmd, bufsize=-1):
54 def Popen4(cmd, bufsize=-1):
55 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
55 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
56 close_fds=closefds,
56 close_fds=closefds,
57 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
57 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
58 stderr=subprocess.STDOUT)
58 stderr=subprocess.STDOUT)
59 p.fromchild = p.stdout
59 p.fromchild = p.stdout
60 p.tochild = p.stdin
60 p.tochild = p.stdin
61 p.childerr = p.stderr
61 p.childerr = p.stderr
62 return p
62 return p
63
63
64 # reserved exit code to skip test (used by hghave)
64 # reserved exit code to skip test (used by hghave)
65 SKIPPED_STATUS = 80
65 SKIPPED_STATUS = 80
66 SKIPPED_PREFIX = 'skipped: '
66 SKIPPED_PREFIX = 'skipped: '
67 FAILED_PREFIX = 'hghave check failed: '
67 FAILED_PREFIX = 'hghave check failed: '
68 PYTHON = sys.executable
68 PYTHON = sys.executable
69
69
70 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
70 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
71
71
72 defaults = {
72 defaults = {
73 'jobs': ('HGTEST_JOBS', 1),
73 'jobs': ('HGTEST_JOBS', 1),
74 'timeout': ('HGTEST_TIMEOUT', 180),
74 'timeout': ('HGTEST_TIMEOUT', 180),
75 'port': ('HGTEST_PORT', 20059),
75 'port': ('HGTEST_PORT', 20059),
76 }
76 }
77
77
78 def parseargs():
78 def parseargs():
79 parser = optparse.OptionParser("%prog [options] [tests]")
79 parser = optparse.OptionParser("%prog [options] [tests]")
80 parser.add_option("-C", "--annotate", action="store_true",
80 parser.add_option("-C", "--annotate", action="store_true",
81 help="output files annotated with coverage")
81 help="output files annotated with coverage")
82 parser.add_option("--child", type="int",
82 parser.add_option("--child", type="int",
83 help="run as child process, summary to given fd")
83 help="run as child process, summary to given fd")
84 parser.add_option("-c", "--cover", action="store_true",
84 parser.add_option("-c", "--cover", action="store_true",
85 help="print a test coverage report")
85 help="print a test coverage report")
86 parser.add_option("-f", "--first", action="store_true",
86 parser.add_option("-f", "--first", action="store_true",
87 help="exit on the first test failure")
87 help="exit on the first test failure")
88 parser.add_option("-i", "--interactive", action="store_true",
88 parser.add_option("-i", "--interactive", action="store_true",
89 help="prompt to accept changed output")
89 help="prompt to accept changed output")
90 parser.add_option("-j", "--jobs", type="int",
90 parser.add_option("-j", "--jobs", type="int",
91 help="number of jobs to run in parallel"
91 help="number of jobs to run in parallel"
92 " (default: $%s or %d)" % defaults['jobs'])
92 " (default: $%s or %d)" % defaults['jobs'])
93 parser.add_option("--keep-tmpdir", action="store_true",
93 parser.add_option("--keep-tmpdir", action="store_true",
94 help="keep temporary directory after running tests"
94 help="keep temporary directory after running tests"
95 " (best used with --tmpdir)")
95 " (best used with --tmpdir)")
96 parser.add_option("-R", "--restart", action="store_true",
96 parser.add_option("-R", "--restart", action="store_true",
97 help="restart at last error")
97 help="restart at last error")
98 parser.add_option("-p", "--port", type="int",
98 parser.add_option("-p", "--port", type="int",
99 help="port on which servers should listen"
99 help="port on which servers should listen"
100 " (default: $%s or %d)" % defaults['port'])
100 " (default: $%s or %d)" % defaults['port'])
101 parser.add_option("-r", "--retest", action="store_true",
101 parser.add_option("-r", "--retest", action="store_true",
102 help="retest failed tests")
102 help="retest failed tests")
103 parser.add_option("-s", "--cover_stdlib", action="store_true",
103 parser.add_option("-s", "--cover_stdlib", action="store_true",
104 help="print a test coverage report inc. standard libraries")
104 help="print a test coverage report inc. standard libraries")
105 parser.add_option("-t", "--timeout", type="int",
105 parser.add_option("-t", "--timeout", type="int",
106 help="kill errant tests after TIMEOUT seconds"
106 help="kill errant tests after TIMEOUT seconds"
107 " (default: $%s or %d)" % defaults['timeout'])
107 " (default: $%s or %d)" % defaults['timeout'])
108 parser.add_option("--tmpdir", type="string",
108 parser.add_option("--tmpdir", type="string",
109 help="run tests in the given temporary directory")
109 help="run tests in the given temporary directory")
110 parser.add_option("-v", "--verbose", action="store_true",
110 parser.add_option("-v", "--verbose", action="store_true",
111 help="output verbose messages")
111 help="output verbose messages")
112 parser.add_option("-n", "--nodiff", action="store_true",
112 parser.add_option("-n", "--nodiff", action="store_true",
113 help="skip showing test changes")
113 help="skip showing test changes")
114 parser.add_option("--with-hg", type="string",
114 parser.add_option("--with-hg", type="string",
115 metavar="HG",
115 metavar="HG",
116 help="test using specified hg script rather than a "
116 help="test using specified hg script rather than a "
117 "temporary installation")
117 "temporary installation")
118 parser.add_option("--local", action="store_true",
118 parser.add_option("--local", action="store_true",
119 help="shortcut for --with-hg=<testdir>/../hg")
119 help="shortcut for --with-hg=<testdir>/../hg")
120 parser.add_option("--pure", action="store_true",
120 parser.add_option("--pure", action="store_true",
121 help="use pure Python code instead of C extensions")
121 help="use pure Python code instead of C extensions")
122 parser.add_option("-3", "--py3k-warnings", action="store_true",
122 parser.add_option("-3", "--py3k-warnings", action="store_true",
123 help="enable Py3k warnings on Python 2.6+")
123 help="enable Py3k warnings on Python 2.6+")
124
124
125 for option, default in defaults.items():
125 for option, default in defaults.items():
126 defaults[option] = int(os.environ.get(*default))
126 defaults[option] = int(os.environ.get(*default))
127 parser.set_defaults(**defaults)
127 parser.set_defaults(**defaults)
128 (options, args) = parser.parse_args()
128 (options, args) = parser.parse_args()
129
129
130 if options.with_hg:
130 if options.with_hg:
131 if not (os.path.isfile(options.with_hg) and
131 if not (os.path.isfile(options.with_hg) and
132 os.access(options.with_hg, os.X_OK)):
132 os.access(options.with_hg, os.X_OK)):
133 parser.error('--with-hg must specify an executable hg script')
133 parser.error('--with-hg must specify an executable hg script')
134 if not os.path.basename(options.with_hg) == 'hg':
134 if not os.path.basename(options.with_hg) == 'hg':
135 sys.stderr.write('warning: --with-hg should specify an hg script')
135 sys.stderr.write('warning: --with-hg should specify an hg script')
136 if options.local:
136 if options.local:
137 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
137 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
138 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
138 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
139 if not os.access(hgbin, os.X_OK):
139 if not os.access(hgbin, os.X_OK):
140 parser.error('--local specified, but %r not found or not executable'
140 parser.error('--local specified, but %r not found or not executable'
141 % hgbin)
141 % hgbin)
142 options.with_hg = hgbin
142 options.with_hg = hgbin
143
143
144 options.anycoverage = (options.cover or
144 options.anycoverage = (options.cover or
145 options.cover_stdlib or
145 options.cover_stdlib or
146 options.annotate)
146 options.annotate)
147
147
148 if options.anycoverage and options.with_hg:
148 if options.anycoverage and options.with_hg:
149 # I'm not sure if this is a fundamental limitation or just a
149 # I'm not sure if this is a fundamental limitation or just a
150 # bug. But I don't want to waste people's time and energy doing
150 # bug. But I don't want to waste people's time and energy doing
151 # test runs that don't give the results they want.
151 # test runs that don't give the results they want.
152 parser.error("sorry, coverage options do not work when --with-hg "
152 parser.error("sorry, coverage options do not work when --with-hg "
153 "or --local specified")
153 "or --local specified")
154
154
155 global vlog
155 global vlog
156 if options.verbose:
156 if options.verbose:
157 if options.jobs > 1 or options.child is not None:
157 if options.jobs > 1 or options.child is not None:
158 pid = "[%d]" % os.getpid()
158 pid = "[%d]" % os.getpid()
159 else:
159 else:
160 pid = None
160 pid = None
161 def vlog(*msg):
161 def vlog(*msg):
162 if pid:
162 if pid:
163 print pid,
163 print pid,
164 for m in msg:
164 for m in msg:
165 print m,
165 print m,
166 print
166 print
167 else:
167 else:
168 vlog = lambda *msg: None
168 vlog = lambda *msg: None
169
169
170 if options.tmpdir:
171 options.tmpdir = os.path.expanduser(options.tmpdir)
172 try:
173 os.makedirs(options.tmpdir)
174 except OSError, err:
175 if err.errno != errno.EEXIST:
176 raise
177
170 if options.jobs < 1:
178 if options.jobs < 1:
171 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
179 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
172 sys.exit(1)
180 sys.exit(1)
173 if options.interactive and options.jobs > 1:
181 if options.interactive and options.jobs > 1:
174 print '(--interactive overrides --jobs)'
182 print '(--interactive overrides --jobs)'
175 options.jobs = 1
183 options.jobs = 1
176 if options.py3k_warnings:
184 if options.py3k_warnings:
177 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
185 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
178 print 'ERROR: Py3k warnings switch can only be used on Python 2.6+'
186 print 'ERROR: Py3k warnings switch can only be used on Python 2.6+'
179 sys.exit(1)
187 sys.exit(1)
180
188
181 return (options, args)
189 return (options, args)
182
190
183 def rename(src, dst):
191 def rename(src, dst):
184 """Like os.rename(), trade atomicity and opened files friendliness
192 """Like os.rename(), trade atomicity and opened files friendliness
185 for existing destination support.
193 for existing destination support.
186 """
194 """
187 shutil.copy(src, dst)
195 shutil.copy(src, dst)
188 os.remove(src)
196 os.remove(src)
189
197
190 def splitnewlines(text):
198 def splitnewlines(text):
191 '''like str.splitlines, but only split on newlines.
199 '''like str.splitlines, but only split on newlines.
192 keep line endings.'''
200 keep line endings.'''
193 i = 0
201 i = 0
194 lines = []
202 lines = []
195 while True:
203 while True:
196 n = text.find('\n', i)
204 n = text.find('\n', i)
197 if n == -1:
205 if n == -1:
198 last = text[i:]
206 last = text[i:]
199 if last:
207 if last:
200 lines.append(last)
208 lines.append(last)
201 return lines
209 return lines
202 lines.append(text[i:n+1])
210 lines.append(text[i:n+1])
203 i = n + 1
211 i = n + 1
204
212
205 def parsehghaveoutput(lines):
213 def parsehghaveoutput(lines):
206 '''Parse hghave log lines.
214 '''Parse hghave log lines.
207 Return tuple of lists (missing, failed):
215 Return tuple of lists (missing, failed):
208 * the missing/unknown features
216 * the missing/unknown features
209 * the features for which existence check failed'''
217 * the features for which existence check failed'''
210 missing = []
218 missing = []
211 failed = []
219 failed = []
212 for line in lines:
220 for line in lines:
213 if line.startswith(SKIPPED_PREFIX):
221 if line.startswith(SKIPPED_PREFIX):
214 line = line.splitlines()[0]
222 line = line.splitlines()[0]
215 missing.append(line[len(SKIPPED_PREFIX):])
223 missing.append(line[len(SKIPPED_PREFIX):])
216 elif line.startswith(FAILED_PREFIX):
224 elif line.startswith(FAILED_PREFIX):
217 line = line.splitlines()[0]
225 line = line.splitlines()[0]
218 failed.append(line[len(FAILED_PREFIX):])
226 failed.append(line[len(FAILED_PREFIX):])
219
227
220 return missing, failed
228 return missing, failed
221
229
222 def showdiff(expected, output):
230 def showdiff(expected, output):
223 for line in difflib.unified_diff(expected, output,
231 for line in difflib.unified_diff(expected, output,
224 "Expected output", "Test output"):
232 "Expected output", "Test output"):
225 sys.stdout.write(line)
233 sys.stdout.write(line)
226
234
227 def findprogram(program):
235 def findprogram(program):
228 """Search PATH for a executable program"""
236 """Search PATH for a executable program"""
229 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
237 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
230 name = os.path.join(p, program)
238 name = os.path.join(p, program)
231 if os.access(name, os.X_OK):
239 if os.access(name, os.X_OK):
232 return name
240 return name
233 return None
241 return None
234
242
235 def checktools():
243 def checktools():
236 # Before we go any further, check for pre-requisite tools
244 # Before we go any further, check for pre-requisite tools
237 # stuff from coreutils (cat, rm, etc) are not tested
245 # stuff from coreutils (cat, rm, etc) are not tested
238 for p in requiredtools:
246 for p in requiredtools:
239 if os.name == 'nt':
247 if os.name == 'nt':
240 p += '.exe'
248 p += '.exe'
241 found = findprogram(p)
249 found = findprogram(p)
242 if found:
250 if found:
243 vlog("# Found prerequisite", p, "at", found)
251 vlog("# Found prerequisite", p, "at", found)
244 else:
252 else:
245 print "WARNING: Did not find prerequisite tool: "+p
253 print "WARNING: Did not find prerequisite tool: "+p
246
254
247 def cleanup(options):
255 def cleanup(options):
248 if not options.keep_tmpdir:
256 if not options.keep_tmpdir:
249 vlog("# Cleaning up HGTMP", HGTMP)
257 vlog("# Cleaning up HGTMP", HGTMP)
250 shutil.rmtree(HGTMP, True)
258 shutil.rmtree(HGTMP, True)
251
259
252 def usecorrectpython():
260 def usecorrectpython():
253 # some tests run python interpreter. they must use same
261 # some tests run python interpreter. they must use same
254 # interpreter we use or bad things will happen.
262 # interpreter we use or bad things will happen.
255 exedir, exename = os.path.split(sys.executable)
263 exedir, exename = os.path.split(sys.executable)
256 if exename == 'python':
264 if exename == 'python':
257 path = findprogram('python')
265 path = findprogram('python')
258 if os.path.dirname(path) == exedir:
266 if os.path.dirname(path) == exedir:
259 return
267 return
260 vlog('# Making python executable in test path use correct Python')
268 vlog('# Making python executable in test path use correct Python')
261 mypython = os.path.join(BINDIR, 'python')
269 mypython = os.path.join(BINDIR, 'python')
262 try:
270 try:
263 os.symlink(sys.executable, mypython)
271 os.symlink(sys.executable, mypython)
264 except AttributeError:
272 except AttributeError:
265 # windows fallback
273 # windows fallback
266 shutil.copyfile(sys.executable, mypython)
274 shutil.copyfile(sys.executable, mypython)
267 shutil.copymode(sys.executable, mypython)
275 shutil.copymode(sys.executable, mypython)
268
276
269 def installhg(options):
277 def installhg(options):
270 vlog("# Performing temporary installation of HG")
278 vlog("# Performing temporary installation of HG")
271 installerrs = os.path.join("tests", "install.err")
279 installerrs = os.path.join("tests", "install.err")
272 pure = options.pure and "--pure" or ""
280 pure = options.pure and "--pure" or ""
273
281
274 # Run installer in hg root
282 # Run installer in hg root
275 script = os.path.realpath(sys.argv[0])
283 script = os.path.realpath(sys.argv[0])
276 hgroot = os.path.dirname(os.path.dirname(script))
284 hgroot = os.path.dirname(os.path.dirname(script))
277 os.chdir(hgroot)
285 os.chdir(hgroot)
278 cmd = ('%s setup.py %s clean --all'
286 cmd = ('%s setup.py %s clean --all'
279 ' install --force --prefix="%s" --install-lib="%s"'
287 ' install --force --prefix="%s" --install-lib="%s"'
280 ' --install-scripts="%s" >%s 2>&1'
288 ' --install-scripts="%s" >%s 2>&1'
281 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
289 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
282 vlog("# Running", cmd)
290 vlog("# Running", cmd)
283 if os.system(cmd) == 0:
291 if os.system(cmd) == 0:
284 if not options.verbose:
292 if not options.verbose:
285 os.remove(installerrs)
293 os.remove(installerrs)
286 else:
294 else:
287 f = open(installerrs)
295 f = open(installerrs)
288 for line in f:
296 for line in f:
289 print line,
297 print line,
290 f.close()
298 f.close()
291 sys.exit(1)
299 sys.exit(1)
292 os.chdir(TESTDIR)
300 os.chdir(TESTDIR)
293
301
294 usecorrectpython()
302 usecorrectpython()
295
303
296 vlog("# Installing dummy diffstat")
304 vlog("# Installing dummy diffstat")
297 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
305 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
298 f.write('#!' + sys.executable + '\n'
306 f.write('#!' + sys.executable + '\n'
299 'import sys\n'
307 'import sys\n'
300 'files = 0\n'
308 'files = 0\n'
301 'for line in sys.stdin:\n'
309 'for line in sys.stdin:\n'
302 ' if line.startswith("diff "):\n'
310 ' if line.startswith("diff "):\n'
303 ' files += 1\n'
311 ' files += 1\n'
304 'sys.stdout.write("files patched: %d\\n" % files)\n')
312 'sys.stdout.write("files patched: %d\\n" % files)\n')
305 f.close()
313 f.close()
306 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
314 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
307
315
308 if options.py3k_warnings and not options.anycoverage:
316 if options.py3k_warnings and not options.anycoverage:
309 vlog("# Updating hg command to enable Py3k Warnings switch")
317 vlog("# Updating hg command to enable Py3k Warnings switch")
310 f = open(os.path.join(BINDIR, 'hg'), 'r')
318 f = open(os.path.join(BINDIR, 'hg'), 'r')
311 lines = [line.rstrip() for line in f]
319 lines = [line.rstrip() for line in f]
312 lines[0] += ' -3'
320 lines[0] += ' -3'
313 f.close()
321 f.close()
314 f = open(os.path.join(BINDIR, 'hg'), 'w')
322 f = open(os.path.join(BINDIR, 'hg'), 'w')
315 for line in lines:
323 for line in lines:
316 f.write(line + '\n')
324 f.write(line + '\n')
317 f.close()
325 f.close()
318
326
319 if options.anycoverage:
327 if options.anycoverage:
320 vlog("# Installing coverage wrapper")
328 vlog("# Installing coverage wrapper")
321 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
329 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
322 if os.path.exists(COVERAGE_FILE):
330 if os.path.exists(COVERAGE_FILE):
323 os.unlink(COVERAGE_FILE)
331 os.unlink(COVERAGE_FILE)
324 # Create a wrapper script to invoke hg via coverage.py
332 # Create a wrapper script to invoke hg via coverage.py
325 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
333 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
326 f = open(os.path.join(BINDIR, 'hg'), 'w')
334 f = open(os.path.join(BINDIR, 'hg'), 'w')
327 f.write('#!' + sys.executable + '\n')
335 f.write('#!' + sys.executable + '\n')
328 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
336 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
329 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
337 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
330 (os.path.join(TESTDIR, 'coverage.py'),
338 (os.path.join(TESTDIR, 'coverage.py'),
331 os.path.join(BINDIR, '_hg.py')))
339 os.path.join(BINDIR, '_hg.py')))
332 f.close()
340 f.close()
333 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
341 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
334
342
335 def outputcoverage(options):
343 def outputcoverage(options):
336
344
337 vlog('# Producing coverage report')
345 vlog('# Producing coverage report')
338 os.chdir(PYTHONDIR)
346 os.chdir(PYTHONDIR)
339
347
340 def covrun(*args):
348 def covrun(*args):
341 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
349 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
342 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
350 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
343 vlog('# Running: %s' % cmd)
351 vlog('# Running: %s' % cmd)
344 os.system(cmd)
352 os.system(cmd)
345
353
346 omit = [BINDIR, TESTDIR, PYTHONDIR]
354 omit = [BINDIR, TESTDIR, PYTHONDIR]
347 if not options.cover_stdlib:
355 if not options.cover_stdlib:
348 # Exclude as system paths (ignoring empty strings seen on win)
356 # Exclude as system paths (ignoring empty strings seen on win)
349 omit += [x for x in sys.path if x != '']
357 omit += [x for x in sys.path if x != '']
350 omit = ','.join(omit)
358 omit = ','.join(omit)
351
359
352 covrun('-c') # combine from parallel processes
360 covrun('-c') # combine from parallel processes
353 for fn in os.listdir(TESTDIR):
361 for fn in os.listdir(TESTDIR):
354 if fn.startswith('.coverage.'):
362 if fn.startswith('.coverage.'):
355 os.unlink(os.path.join(TESTDIR, fn))
363 os.unlink(os.path.join(TESTDIR, fn))
356
364
357 covrun('-i', '-r', '"--omit=%s"' % omit) # report
365 covrun('-i', '-r', '"--omit=%s"' % omit) # report
358 if options.annotate:
366 if options.annotate:
359 adir = os.path.join(TESTDIR, 'annotated')
367 adir = os.path.join(TESTDIR, 'annotated')
360 if not os.path.isdir(adir):
368 if not os.path.isdir(adir):
361 os.mkdir(adir)
369 os.mkdir(adir)
362 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
370 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
363
371
364 class Timeout(Exception):
372 class Timeout(Exception):
365 pass
373 pass
366
374
367 def alarmed(signum, frame):
375 def alarmed(signum, frame):
368 raise Timeout
376 raise Timeout
369
377
370 def run(cmd, options):
378 def run(cmd, options):
371 """Run command in a sub-process, capturing the output (stdout and stderr).
379 """Run command in a sub-process, capturing the output (stdout and stderr).
372 Return the exist code, and output."""
380 Return the exist code, and output."""
373 # TODO: Use subprocess.Popen if we're running on Python 2.4
381 # TODO: Use subprocess.Popen if we're running on Python 2.4
374 if os.name == 'nt' or sys.platform.startswith('java'):
382 if os.name == 'nt' or sys.platform.startswith('java'):
375 tochild, fromchild = os.popen4(cmd)
383 tochild, fromchild = os.popen4(cmd)
376 tochild.close()
384 tochild.close()
377 output = fromchild.read()
385 output = fromchild.read()
378 ret = fromchild.close()
386 ret = fromchild.close()
379 if ret == None:
387 if ret == None:
380 ret = 0
388 ret = 0
381 else:
389 else:
382 proc = Popen4(cmd)
390 proc = Popen4(cmd)
383 try:
391 try:
384 output = ''
392 output = ''
385 proc.tochild.close()
393 proc.tochild.close()
386 output = proc.fromchild.read()
394 output = proc.fromchild.read()
387 ret = proc.wait()
395 ret = proc.wait()
388 if os.WIFEXITED(ret):
396 if os.WIFEXITED(ret):
389 ret = os.WEXITSTATUS(ret)
397 ret = os.WEXITSTATUS(ret)
390 except Timeout:
398 except Timeout:
391 vlog('# Process %d timed out - killing it' % proc.pid)
399 vlog('# Process %d timed out - killing it' % proc.pid)
392 os.kill(proc.pid, signal.SIGTERM)
400 os.kill(proc.pid, signal.SIGTERM)
393 ret = proc.wait()
401 ret = proc.wait()
394 if ret == 0:
402 if ret == 0:
395 ret = signal.SIGTERM << 8
403 ret = signal.SIGTERM << 8
396 output += ("\n### Abort: timeout after %d seconds.\n"
404 output += ("\n### Abort: timeout after %d seconds.\n"
397 % options.timeout)
405 % options.timeout)
398 return ret, splitnewlines(output)
406 return ret, splitnewlines(output)
399
407
400 def runone(options, test, skips, fails):
408 def runone(options, test, skips, fails):
401 '''tristate output:
409 '''tristate output:
402 None -> skipped
410 None -> skipped
403 True -> passed
411 True -> passed
404 False -> failed'''
412 False -> failed'''
405
413
406 def skip(msg):
414 def skip(msg):
407 if not options.verbose:
415 if not options.verbose:
408 skips.append((test, msg))
416 skips.append((test, msg))
409 else:
417 else:
410 print "\nSkipping %s: %s" % (test, msg)
418 print "\nSkipping %s: %s" % (test, msg)
411 return None
419 return None
412
420
413 def fail(msg):
421 def fail(msg):
414 fails.append((test, msg))
422 fails.append((test, msg))
415 if not options.nodiff:
423 if not options.nodiff:
416 print "\nERROR: %s %s" % (test, msg)
424 print "\nERROR: %s %s" % (test, msg)
417 return None
425 return None
418
426
419 vlog("# Test", test)
427 vlog("# Test", test)
420
428
421 # create a fresh hgrc
429 # create a fresh hgrc
422 hgrc = open(HGRCPATH, 'w+')
430 hgrc = open(HGRCPATH, 'w+')
423 hgrc.write('[ui]\n')
431 hgrc.write('[ui]\n')
424 hgrc.write('slash = True\n')
432 hgrc.write('slash = True\n')
425 hgrc.write('[defaults]\n')
433 hgrc.write('[defaults]\n')
426 hgrc.write('backout = -d "0 0"\n')
434 hgrc.write('backout = -d "0 0"\n')
427 hgrc.write('commit = -d "0 0"\n')
435 hgrc.write('commit = -d "0 0"\n')
428 hgrc.write('tag = -d "0 0"\n')
436 hgrc.write('tag = -d "0 0"\n')
429 hgrc.close()
437 hgrc.close()
430
438
431 err = os.path.join(TESTDIR, test+".err")
439 err = os.path.join(TESTDIR, test+".err")
432 ref = os.path.join(TESTDIR, test+".out")
440 ref = os.path.join(TESTDIR, test+".out")
433 testpath = os.path.join(TESTDIR, test)
441 testpath = os.path.join(TESTDIR, test)
434
442
435 if os.path.exists(err):
443 if os.path.exists(err):
436 os.remove(err) # Remove any previous output files
444 os.remove(err) # Remove any previous output files
437
445
438 # Make a tmp subdirectory to work in
446 # Make a tmp subdirectory to work in
439 tmpd = os.path.join(HGTMP, test)
447 tmpd = os.path.join(HGTMP, test)
440 os.mkdir(tmpd)
448 os.mkdir(tmpd)
441 os.chdir(tmpd)
449 os.chdir(tmpd)
442
450
443 try:
451 try:
444 tf = open(testpath)
452 tf = open(testpath)
445 firstline = tf.readline().rstrip()
453 firstline = tf.readline().rstrip()
446 tf.close()
454 tf.close()
447 except:
455 except:
448 firstline = ''
456 firstline = ''
449 lctest = test.lower()
457 lctest = test.lower()
450
458
451 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
459 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
452 py3kswitch = options.py3k_warnings and ' -3' or ''
460 py3kswitch = options.py3k_warnings and ' -3' or ''
453 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
461 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
454 elif lctest.endswith('.bat'):
462 elif lctest.endswith('.bat'):
455 # do not run batch scripts on non-windows
463 # do not run batch scripts on non-windows
456 if os.name != 'nt':
464 if os.name != 'nt':
457 return skip("batch script")
465 return skip("batch script")
458 # To reliably get the error code from batch files on WinXP,
466 # To reliably get the error code from batch files on WinXP,
459 # the "cmd /c call" prefix is needed. Grrr
467 # the "cmd /c call" prefix is needed. Grrr
460 cmd = 'cmd /c call "%s"' % testpath
468 cmd = 'cmd /c call "%s"' % testpath
461 else:
469 else:
462 # do not run shell scripts on windows
470 # do not run shell scripts on windows
463 if os.name == 'nt':
471 if os.name == 'nt':
464 return skip("shell script")
472 return skip("shell script")
465 # do not try to run non-executable programs
473 # do not try to run non-executable programs
466 if not os.path.exists(testpath):
474 if not os.path.exists(testpath):
467 return fail("does not exist")
475 return fail("does not exist")
468 elif not os.access(testpath, os.X_OK):
476 elif not os.access(testpath, os.X_OK):
469 return skip("not executable")
477 return skip("not executable")
470 cmd = '"%s"' % testpath
478 cmd = '"%s"' % testpath
471
479
472 if options.timeout > 0:
480 if options.timeout > 0:
473 signal.alarm(options.timeout)
481 signal.alarm(options.timeout)
474
482
475 vlog("# Running", cmd)
483 vlog("# Running", cmd)
476 ret, out = run(cmd, options)
484 ret, out = run(cmd, options)
477 vlog("# Ret was:", ret)
485 vlog("# Ret was:", ret)
478
486
479 if options.timeout > 0:
487 if options.timeout > 0:
480 signal.alarm(0)
488 signal.alarm(0)
481
489
482 mark = '.'
490 mark = '.'
483
491
484 skipped = (ret == SKIPPED_STATUS)
492 skipped = (ret == SKIPPED_STATUS)
485 # If reference output file exists, check test output against it
493 # If reference output file exists, check test output against it
486 if os.path.exists(ref):
494 if os.path.exists(ref):
487 f = open(ref, "r")
495 f = open(ref, "r")
488 refout = splitnewlines(f.read())
496 refout = splitnewlines(f.read())
489 f.close()
497 f.close()
490 else:
498 else:
491 refout = []
499 refout = []
492 if skipped:
500 if skipped:
493 mark = 's'
501 mark = 's'
494 missing, failed = parsehghaveoutput(out)
502 missing, failed = parsehghaveoutput(out)
495 if not missing:
503 if not missing:
496 missing = ['irrelevant']
504 missing = ['irrelevant']
497 if failed:
505 if failed:
498 fail("hghave failed checking for %s" % failed[-1])
506 fail("hghave failed checking for %s" % failed[-1])
499 skipped = False
507 skipped = False
500 else:
508 else:
501 skip(missing[-1])
509 skip(missing[-1])
502 elif out != refout:
510 elif out != refout:
503 mark = '!'
511 mark = '!'
504 if ret:
512 if ret:
505 fail("output changed and returned error code %d" % ret)
513 fail("output changed and returned error code %d" % ret)
506 else:
514 else:
507 fail("output changed")
515 fail("output changed")
508 if not options.nodiff:
516 if not options.nodiff:
509 showdiff(refout, out)
517 showdiff(refout, out)
510 ret = 1
518 ret = 1
511 elif ret:
519 elif ret:
512 mark = '!'
520 mark = '!'
513 fail("returned error code %d" % ret)
521 fail("returned error code %d" % ret)
514
522
515 if not options.verbose:
523 if not options.verbose:
516 sys.stdout.write(mark)
524 sys.stdout.write(mark)
517 sys.stdout.flush()
525 sys.stdout.flush()
518
526
519 if ret != 0 and not skipped:
527 if ret != 0 and not skipped:
520 # Save errors to a file for diagnosis
528 # Save errors to a file for diagnosis
521 f = open(err, "wb")
529 f = open(err, "wb")
522 for line in out:
530 for line in out:
523 f.write(line)
531 f.write(line)
524 f.close()
532 f.close()
525
533
526 # Kill off any leftover daemon processes
534 # Kill off any leftover daemon processes
527 try:
535 try:
528 fp = open(DAEMON_PIDS)
536 fp = open(DAEMON_PIDS)
529 for line in fp:
537 for line in fp:
530 try:
538 try:
531 pid = int(line)
539 pid = int(line)
532 except ValueError:
540 except ValueError:
533 continue
541 continue
534 try:
542 try:
535 os.kill(pid, 0)
543 os.kill(pid, 0)
536 vlog('# Killing daemon process %d' % pid)
544 vlog('# Killing daemon process %d' % pid)
537 os.kill(pid, signal.SIGTERM)
545 os.kill(pid, signal.SIGTERM)
538 time.sleep(0.25)
546 time.sleep(0.25)
539 os.kill(pid, 0)
547 os.kill(pid, 0)
540 vlog('# Daemon process %d is stuck - really killing it' % pid)
548 vlog('# Daemon process %d is stuck - really killing it' % pid)
541 os.kill(pid, signal.SIGKILL)
549 os.kill(pid, signal.SIGKILL)
542 except OSError, err:
550 except OSError, err:
543 if err.errno != errno.ESRCH:
551 if err.errno != errno.ESRCH:
544 raise
552 raise
545 fp.close()
553 fp.close()
546 os.unlink(DAEMON_PIDS)
554 os.unlink(DAEMON_PIDS)
547 except IOError:
555 except IOError:
548 pass
556 pass
549
557
550 os.chdir(TESTDIR)
558 os.chdir(TESTDIR)
551 if not options.keep_tmpdir:
559 if not options.keep_tmpdir:
552 shutil.rmtree(tmpd, True)
560 shutil.rmtree(tmpd, True)
553 if skipped:
561 if skipped:
554 return None
562 return None
555 return ret == 0
563 return ret == 0
556
564
557 _hgpath = None
565 _hgpath = None
558
566
559 def _gethgpath():
567 def _gethgpath():
560 """Return the path to the mercurial package that is actually found by
568 """Return the path to the mercurial package that is actually found by
561 the current Python interpreter."""
569 the current Python interpreter."""
562 global _hgpath
570 global _hgpath
563 if _hgpath is not None:
571 if _hgpath is not None:
564 return _hgpath
572 return _hgpath
565
573
566 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
574 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
567 pipe = os.popen(cmd % PYTHON)
575 pipe = os.popen(cmd % PYTHON)
568 try:
576 try:
569 _hgpath = pipe.read().strip()
577 _hgpath = pipe.read().strip()
570 finally:
578 finally:
571 pipe.close()
579 pipe.close()
572 return _hgpath
580 return _hgpath
573
581
574 def _checkhglib(verb):
582 def _checkhglib(verb):
575 """Ensure that the 'mercurial' package imported by python is
583 """Ensure that the 'mercurial' package imported by python is
576 the one we expect it to be. If not, print a warning to stderr."""
584 the one we expect it to be. If not, print a warning to stderr."""
577 expecthg = os.path.join(PYTHONDIR, 'mercurial')
585 expecthg = os.path.join(PYTHONDIR, 'mercurial')
578 actualhg = _gethgpath()
586 actualhg = _gethgpath()
579 if actualhg != expecthg:
587 if actualhg != expecthg:
580 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
588 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
581 ' (expected %s)\n'
589 ' (expected %s)\n'
582 % (verb, actualhg, expecthg))
590 % (verb, actualhg, expecthg))
583
591
584 def runchildren(options, tests):
592 def runchildren(options, tests):
585 if INST:
593 if INST:
586 installhg(options)
594 installhg(options)
587 _checkhglib("Testing")
595 _checkhglib("Testing")
588
596
589 optcopy = dict(options.__dict__)
597 optcopy = dict(options.__dict__)
590 optcopy['jobs'] = 1
598 optcopy['jobs'] = 1
591 if optcopy['with_hg'] is None:
599 if optcopy['with_hg'] is None:
592 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
600 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
593 opts = []
601 opts = []
594 for opt, value in optcopy.iteritems():
602 for opt, value in optcopy.iteritems():
595 name = '--' + opt.replace('_', '-')
603 name = '--' + opt.replace('_', '-')
596 if value is True:
604 if value is True:
597 opts.append(name)
605 opts.append(name)
598 elif value is not None:
606 elif value is not None:
599 opts.append(name + '=' + str(value))
607 opts.append(name + '=' + str(value))
600
608
601 tests.reverse()
609 tests.reverse()
602 jobs = [[] for j in xrange(options.jobs)]
610 jobs = [[] for j in xrange(options.jobs)]
603 while tests:
611 while tests:
604 for job in jobs:
612 for job in jobs:
605 if not tests: break
613 if not tests: break
606 job.append(tests.pop())
614 job.append(tests.pop())
607 fps = {}
615 fps = {}
608 for j, job in enumerate(jobs):
616 for j, job in enumerate(jobs):
609 if not job:
617 if not job:
610 continue
618 continue
611 rfd, wfd = os.pipe()
619 rfd, wfd = os.pipe()
612 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
620 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
613 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
621 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
614 vlog(' '.join(cmdline))
622 vlog(' '.join(cmdline))
615 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
623 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
616 os.close(wfd)
624 os.close(wfd)
617 failures = 0
625 failures = 0
618 tested, skipped, failed = 0, 0, 0
626 tested, skipped, failed = 0, 0, 0
619 skips = []
627 skips = []
620 fails = []
628 fails = []
621 while fps:
629 while fps:
622 pid, status = os.wait()
630 pid, status = os.wait()
623 fp = fps.pop(pid)
631 fp = fps.pop(pid)
624 l = fp.read().splitlines()
632 l = fp.read().splitlines()
625 test, skip, fail = map(int, l[:3])
633 test, skip, fail = map(int, l[:3])
626 split = -fail or len(l)
634 split = -fail or len(l)
627 for s in l[3:split]:
635 for s in l[3:split]:
628 skips.append(s.split(" ", 1))
636 skips.append(s.split(" ", 1))
629 for s in l[split:]:
637 for s in l[split:]:
630 fails.append(s.split(" ", 1))
638 fails.append(s.split(" ", 1))
631 tested += test
639 tested += test
632 skipped += skip
640 skipped += skip
633 failed += fail
641 failed += fail
634 vlog('pid %d exited, status %d' % (pid, status))
642 vlog('pid %d exited, status %d' % (pid, status))
635 failures |= status
643 failures |= status
636 print
644 print
637 for s in skips:
645 for s in skips:
638 print "Skipped %s: %s" % (s[0], s[1])
646 print "Skipped %s: %s" % (s[0], s[1])
639 for s in fails:
647 for s in fails:
640 print "Failed %s: %s" % (s[0], s[1])
648 print "Failed %s: %s" % (s[0], s[1])
641
649
642 _checkhglib("Tested")
650 _checkhglib("Tested")
643 print "# Ran %d tests, %d skipped, %d failed." % (
651 print "# Ran %d tests, %d skipped, %d failed." % (
644 tested, skipped, failed)
652 tested, skipped, failed)
645 sys.exit(failures != 0)
653 sys.exit(failures != 0)
646
654
647 def runtests(options, tests):
655 def runtests(options, tests):
648 global DAEMON_PIDS, HGRCPATH
656 global DAEMON_PIDS, HGRCPATH
649 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
657 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
650 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
658 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
651
659
652 try:
660 try:
653 if INST:
661 if INST:
654 installhg(options)
662 installhg(options)
655 _checkhglib("Testing")
663 _checkhglib("Testing")
656
664
657 if options.timeout > 0:
665 if options.timeout > 0:
658 try:
666 try:
659 signal.signal(signal.SIGALRM, alarmed)
667 signal.signal(signal.SIGALRM, alarmed)
660 vlog('# Running each test with %d second timeout' %
668 vlog('# Running each test with %d second timeout' %
661 options.timeout)
669 options.timeout)
662 except AttributeError:
670 except AttributeError:
663 print 'WARNING: cannot run tests with timeouts'
671 print 'WARNING: cannot run tests with timeouts'
664 options.timeout = 0
672 options.timeout = 0
665
673
666 tested = 0
674 tested = 0
667 failed = 0
675 failed = 0
668 skipped = 0
676 skipped = 0
669
677
670 if options.restart:
678 if options.restart:
671 orig = list(tests)
679 orig = list(tests)
672 while tests:
680 while tests:
673 if os.path.exists(tests[0] + ".err"):
681 if os.path.exists(tests[0] + ".err"):
674 break
682 break
675 tests.pop(0)
683 tests.pop(0)
676 if not tests:
684 if not tests:
677 print "running all tests"
685 print "running all tests"
678 tests = orig
686 tests = orig
679
687
680 skips = []
688 skips = []
681 fails = []
689 fails = []
682 for test in tests:
690 for test in tests:
683 if options.retest and not os.path.exists(test + ".err"):
691 if options.retest and not os.path.exists(test + ".err"):
684 skipped += 1
692 skipped += 1
685 continue
693 continue
686 ret = runone(options, test, skips, fails)
694 ret = runone(options, test, skips, fails)
687 if ret is None:
695 if ret is None:
688 skipped += 1
696 skipped += 1
689 elif not ret:
697 elif not ret:
690 if options.interactive:
698 if options.interactive:
691 print "Accept this change? [n] ",
699 print "Accept this change? [n] ",
692 answer = sys.stdin.readline().strip()
700 answer = sys.stdin.readline().strip()
693 if answer.lower() in "y yes".split():
701 if answer.lower() in "y yes".split():
694 rename(test + ".err", test + ".out")
702 rename(test + ".err", test + ".out")
695 tested += 1
703 tested += 1
696 fails.pop()
704 fails.pop()
697 continue
705 continue
698 failed += 1
706 failed += 1
699 if options.first:
707 if options.first:
700 break
708 break
701 tested += 1
709 tested += 1
702
710
703 if options.child:
711 if options.child:
704 fp = os.fdopen(options.child, 'w')
712 fp = os.fdopen(options.child, 'w')
705 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
713 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
706 for s in skips:
714 for s in skips:
707 fp.write("%s %s\n" % s)
715 fp.write("%s %s\n" % s)
708 for s in fails:
716 for s in fails:
709 fp.write("%s %s\n" % s)
717 fp.write("%s %s\n" % s)
710 fp.close()
718 fp.close()
711 else:
719 else:
712 print
720 print
713 for s in skips:
721 for s in skips:
714 print "Skipped %s: %s" % s
722 print "Skipped %s: %s" % s
715 for s in fails:
723 for s in fails:
716 print "Failed %s: %s" % s
724 print "Failed %s: %s" % s
717 _checkhglib("Tested")
725 _checkhglib("Tested")
718 print "# Ran %d tests, %d skipped, %d failed." % (
726 print "# Ran %d tests, %d skipped, %d failed." % (
719 tested, skipped, failed)
727 tested, skipped, failed)
720
728
721 if options.anycoverage:
729 if options.anycoverage:
722 outputcoverage(options)
730 outputcoverage(options)
723 except KeyboardInterrupt:
731 except KeyboardInterrupt:
724 failed = True
732 failed = True
725 print "\ninterrupted!"
733 print "\ninterrupted!"
726
734
727 if failed:
735 if failed:
728 sys.exit(1)
736 sys.exit(1)
729
737
730 def main():
738 def main():
731 (options, args) = parseargs()
739 (options, args) = parseargs()
732 if not options.child:
740 if not options.child:
733 os.umask(022)
741 os.umask(022)
734
742
735 checktools()
743 checktools()
736
744
737 # Reset some environment variables to well-known values so that
745 # Reset some environment variables to well-known values so that
738 # the tests produce repeatable output.
746 # the tests produce repeatable output.
739 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
747 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
740 os.environ['TZ'] = 'GMT'
748 os.environ['TZ'] = 'GMT'
741 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
749 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
742 os.environ['CDPATH'] = ''
750 os.environ['CDPATH'] = ''
743
751
744 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
752 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
745 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
753 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
746 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
754 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
747 options.tmpdir))
755 options.tmpdir))
748 DAEMON_PIDS = None
756 DAEMON_PIDS = None
749 HGRCPATH = None
757 HGRCPATH = None
750
758
751 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
759 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
752 os.environ["HGMERGE"] = "internal:merge"
760 os.environ["HGMERGE"] = "internal:merge"
753 os.environ["HGUSER"] = "test"
761 os.environ["HGUSER"] = "test"
754 os.environ["HGENCODING"] = "ascii"
762 os.environ["HGENCODING"] = "ascii"
755 os.environ["HGENCODINGMODE"] = "strict"
763 os.environ["HGENCODINGMODE"] = "strict"
756 os.environ["HGPORT"] = str(options.port)
764 os.environ["HGPORT"] = str(options.port)
757 os.environ["HGPORT1"] = str(options.port + 1)
765 os.environ["HGPORT1"] = str(options.port + 1)
758 os.environ["HGPORT2"] = str(options.port + 2)
766 os.environ["HGPORT2"] = str(options.port + 2)
759
767
760 if options.with_hg:
768 if options.with_hg:
761 INST = None
769 INST = None
762 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
770 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
763
771
764 # This looks redundant with how Python initializes sys.path from
772 # This looks redundant with how Python initializes sys.path from
765 # the location of the script being executed. Needed because the
773 # the location of the script being executed. Needed because the
766 # "hg" specified by --with-hg is not the only Python script
774 # "hg" specified by --with-hg is not the only Python script
767 # executed in the test suite that needs to import 'mercurial'
775 # executed in the test suite that needs to import 'mercurial'
768 # ... which means it's not really redundant at all.
776 # ... which means it's not really redundant at all.
769 PYTHONDIR = BINDIR
777 PYTHONDIR = BINDIR
770 else:
778 else:
771 INST = os.path.join(HGTMP, "install")
779 INST = os.path.join(HGTMP, "install")
772 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
780 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
773 PYTHONDIR = os.path.join(INST, "lib", "python")
781 PYTHONDIR = os.path.join(INST, "lib", "python")
774
782
775 os.environ["BINDIR"] = BINDIR
783 os.environ["BINDIR"] = BINDIR
776 os.environ["PYTHON"] = PYTHON
784 os.environ["PYTHON"] = PYTHON
777
785
778 if not options.child:
786 if not options.child:
779 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
787 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
780 os.environ["PATH"] = os.pathsep.join(path)
788 os.environ["PATH"] = os.pathsep.join(path)
781
789
782 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
790 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
783 # can run .../tests/run-tests.py test-foo where test-foo
791 # can run .../tests/run-tests.py test-foo where test-foo
784 # adds an extension to HGRC
792 # adds an extension to HGRC
785 pypath = [PYTHONDIR, TESTDIR]
793 pypath = [PYTHONDIR, TESTDIR]
786 # We have to augment PYTHONPATH, rather than simply replacing
794 # We have to augment PYTHONPATH, rather than simply replacing
787 # it, in case external libraries are only available via current
795 # it, in case external libraries are only available via current
788 # PYTHONPATH. (In particular, the Subversion bindings on OS X
796 # PYTHONPATH. (In particular, the Subversion bindings on OS X
789 # are in /opt/subversion.)
797 # are in /opt/subversion.)
790 oldpypath = os.environ.get('PYTHONPATH')
798 oldpypath = os.environ.get('PYTHONPATH')
791 if oldpypath:
799 if oldpypath:
792 pypath.append(oldpypath)
800 pypath.append(oldpypath)
793 os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
801 os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
794
802
795 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
803 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
796
804
797 if len(args) == 0:
805 if len(args) == 0:
798 args = os.listdir(".")
806 args = os.listdir(".")
799 args.sort()
807 args.sort()
800
808
801 tests = []
809 tests = []
802 for test in args:
810 for test in args:
803 if (test.startswith("test-") and '~' not in test and
811 if (test.startswith("test-") and '~' not in test and
804 ('.' not in test or test.endswith('.py') or
812 ('.' not in test or test.endswith('.py') or
805 test.endswith('.bat'))):
813 test.endswith('.bat'))):
806 tests.append(test)
814 tests.append(test)
807 if not tests:
815 if not tests:
808 print "# Ran 0 tests, 0 skipped, 0 failed."
816 print "# Ran 0 tests, 0 skipped, 0 failed."
809 return
817 return
810
818
811 vlog("# Using TESTDIR", TESTDIR)
819 vlog("# Using TESTDIR", TESTDIR)
812 vlog("# Using HGTMP", HGTMP)
820 vlog("# Using HGTMP", HGTMP)
813 vlog("# Using PATH", os.environ["PATH"])
821 vlog("# Using PATH", os.environ["PATH"])
814 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
822 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
815
823
816 try:
824 try:
817 if len(tests) > 1 and options.jobs > 1:
825 if len(tests) > 1 and options.jobs > 1:
818 runchildren(options, tests)
826 runchildren(options, tests)
819 else:
827 else:
820 runtests(options, tests)
828 runtests(options, tests)
821 finally:
829 finally:
822 cleanup(options)
830 cleanup(options)
823
831
824 main()
832 main()
General Comments 0
You need to be logged in to leave comments. Login now