##// END OF EJS Templates
run-tests.py: correctly handle list options with parallel tasks
Augie Fackler -
r14192:38e387a6 default
parent child Browse files
Show More
@@ -1,1159 +1,1162 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 or any later version.
8 # GNU General Public License version 2 or any later version.
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 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 #
38 #
39 # (You could use any subset of the tests: test-s* happens to match
39 # (You could use any subset of the tests: test-s* happens to match
40 # enough that it's worth doing parallel runs, few enough that it
40 # enough that it's worth doing parallel runs, few enough that it
41 # completes fairly quickly, includes both shell and Python scripts, and
41 # completes fairly quickly, includes both shell and Python scripts, and
42 # includes some scripts that run daemon processes.)
42 # includes some scripts that run daemon processes.)
43
43
44 from distutils import version
44 from distutils import version
45 import difflib
45 import difflib
46 import errno
46 import errno
47 import optparse
47 import optparse
48 import os
48 import os
49 import shutil
49 import shutil
50 import subprocess
50 import subprocess
51 import signal
51 import signal
52 import sys
52 import sys
53 import tempfile
53 import tempfile
54 import time
54 import time
55 import re
55 import re
56 import threading
56 import threading
57
57
58 processlock = threading.Lock()
58 processlock = threading.Lock()
59
59
60 closefds = os.name == 'posix'
60 closefds = os.name == 'posix'
61 def Popen4(cmd, wd, timeout):
61 def Popen4(cmd, wd, timeout):
62 processlock.acquire()
62 processlock.acquire()
63 orig = os.getcwd()
63 orig = os.getcwd()
64 os.chdir(wd)
64 os.chdir(wd)
65 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
65 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
66 close_fds=closefds,
66 close_fds=closefds,
67 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
67 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
68 stderr=subprocess.STDOUT)
68 stderr=subprocess.STDOUT)
69 os.chdir(orig)
69 os.chdir(orig)
70 processlock.release()
70 processlock.release()
71
71
72 p.fromchild = p.stdout
72 p.fromchild = p.stdout
73 p.tochild = p.stdin
73 p.tochild = p.stdin
74 p.childerr = p.stderr
74 p.childerr = p.stderr
75
75
76 if timeout:
76 if timeout:
77 p.timeout = False
77 p.timeout = False
78 def t():
78 def t():
79 start = time.time()
79 start = time.time()
80 while time.time() - start < timeout and p.returncode is None:
80 while time.time() - start < timeout and p.returncode is None:
81 time.sleep(1)
81 time.sleep(1)
82 p.timeout = True
82 p.timeout = True
83 if p.returncode is None:
83 if p.returncode is None:
84 try:
84 try:
85 p.terminate()
85 p.terminate()
86 except OSError:
86 except OSError:
87 pass
87 pass
88 threading.Thread(target=t).start()
88 threading.Thread(target=t).start()
89
89
90 return p
90 return p
91
91
92 # reserved exit code to skip test (used by hghave)
92 # reserved exit code to skip test (used by hghave)
93 SKIPPED_STATUS = 80
93 SKIPPED_STATUS = 80
94 SKIPPED_PREFIX = 'skipped: '
94 SKIPPED_PREFIX = 'skipped: '
95 FAILED_PREFIX = 'hghave check failed: '
95 FAILED_PREFIX = 'hghave check failed: '
96 PYTHON = sys.executable
96 PYTHON = sys.executable
97 IMPL_PATH = 'PYTHONPATH'
97 IMPL_PATH = 'PYTHONPATH'
98 if 'java' in sys.platform:
98 if 'java' in sys.platform:
99 IMPL_PATH = 'JYTHONPATH'
99 IMPL_PATH = 'JYTHONPATH'
100
100
101 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
101 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
102
102
103 defaults = {
103 defaults = {
104 'jobs': ('HGTEST_JOBS', 1),
104 'jobs': ('HGTEST_JOBS', 1),
105 'timeout': ('HGTEST_TIMEOUT', 180),
105 'timeout': ('HGTEST_TIMEOUT', 180),
106 'port': ('HGTEST_PORT', 20059),
106 'port': ('HGTEST_PORT', 20059),
107 }
107 }
108
108
109 def parseargs():
109 def parseargs():
110 parser = optparse.OptionParser("%prog [options] [tests]")
110 parser = optparse.OptionParser("%prog [options] [tests]")
111
111
112 # keep these sorted
112 # keep these sorted
113 parser.add_option("--blacklist", action="append",
113 parser.add_option("--blacklist", action="append",
114 help="skip tests listed in the specified blacklist file")
114 help="skip tests listed in the specified blacklist file")
115 parser.add_option("-C", "--annotate", action="store_true",
115 parser.add_option("-C", "--annotate", action="store_true",
116 help="output files annotated with coverage")
116 help="output files annotated with coverage")
117 parser.add_option("--child", type="int",
117 parser.add_option("--child", type="int",
118 help="run as child process, summary to given fd")
118 help="run as child process, summary to given fd")
119 parser.add_option("-c", "--cover", action="store_true",
119 parser.add_option("-c", "--cover", action="store_true",
120 help="print a test coverage report")
120 help="print a test coverage report")
121 parser.add_option("-d", "--debug", action="store_true",
121 parser.add_option("-d", "--debug", action="store_true",
122 help="debug mode: write output of test scripts to console"
122 help="debug mode: write output of test scripts to console"
123 " rather than capturing and diff'ing it (disables timeout)")
123 " rather than capturing and diff'ing it (disables timeout)")
124 parser.add_option("-f", "--first", action="store_true",
124 parser.add_option("-f", "--first", action="store_true",
125 help="exit on the first test failure")
125 help="exit on the first test failure")
126 parser.add_option("--inotify", action="store_true",
126 parser.add_option("--inotify", action="store_true",
127 help="enable inotify extension when running tests")
127 help="enable inotify extension when running tests")
128 parser.add_option("-i", "--interactive", action="store_true",
128 parser.add_option("-i", "--interactive", action="store_true",
129 help="prompt to accept changed output")
129 help="prompt to accept changed output")
130 parser.add_option("-j", "--jobs", type="int",
130 parser.add_option("-j", "--jobs", type="int",
131 help="number of jobs to run in parallel"
131 help="number of jobs to run in parallel"
132 " (default: $%s or %d)" % defaults['jobs'])
132 " (default: $%s or %d)" % defaults['jobs'])
133 parser.add_option("--keep-tmpdir", action="store_true",
133 parser.add_option("--keep-tmpdir", action="store_true",
134 help="keep temporary directory after running tests")
134 help="keep temporary directory after running tests")
135 parser.add_option("-k", "--keywords",
135 parser.add_option("-k", "--keywords",
136 help="run tests matching keywords")
136 help="run tests matching keywords")
137 parser.add_option("-l", "--local", action="store_true",
137 parser.add_option("-l", "--local", action="store_true",
138 help="shortcut for --with-hg=<testdir>/../hg")
138 help="shortcut for --with-hg=<testdir>/../hg")
139 parser.add_option("-n", "--nodiff", action="store_true",
139 parser.add_option("-n", "--nodiff", action="store_true",
140 help="skip showing test changes")
140 help="skip showing test changes")
141 parser.add_option("-p", "--port", type="int",
141 parser.add_option("-p", "--port", type="int",
142 help="port on which servers should listen"
142 help="port on which servers should listen"
143 " (default: $%s or %d)" % defaults['port'])
143 " (default: $%s or %d)" % defaults['port'])
144 parser.add_option("--pure", action="store_true",
144 parser.add_option("--pure", action="store_true",
145 help="use pure Python code instead of C extensions")
145 help="use pure Python code instead of C extensions")
146 parser.add_option("-R", "--restart", action="store_true",
146 parser.add_option("-R", "--restart", action="store_true",
147 help="restart at last error")
147 help="restart at last error")
148 parser.add_option("-r", "--retest", action="store_true",
148 parser.add_option("-r", "--retest", action="store_true",
149 help="retest failed tests")
149 help="retest failed tests")
150 parser.add_option("-S", "--noskips", action="store_true",
150 parser.add_option("-S", "--noskips", action="store_true",
151 help="don't report skip tests verbosely")
151 help="don't report skip tests verbosely")
152 parser.add_option("-t", "--timeout", type="int",
152 parser.add_option("-t", "--timeout", type="int",
153 help="kill errant tests after TIMEOUT seconds"
153 help="kill errant tests after TIMEOUT seconds"
154 " (default: $%s or %d)" % defaults['timeout'])
154 " (default: $%s or %d)" % defaults['timeout'])
155 parser.add_option("--tmpdir", type="string",
155 parser.add_option("--tmpdir", type="string",
156 help="run tests in the given temporary directory"
156 help="run tests in the given temporary directory"
157 " (implies --keep-tmpdir)")
157 " (implies --keep-tmpdir)")
158 parser.add_option("-v", "--verbose", action="store_true",
158 parser.add_option("-v", "--verbose", action="store_true",
159 help="output verbose messages")
159 help="output verbose messages")
160 parser.add_option("--view", type="string",
160 parser.add_option("--view", type="string",
161 help="external diff viewer")
161 help="external diff viewer")
162 parser.add_option("--with-hg", type="string",
162 parser.add_option("--with-hg", type="string",
163 metavar="HG",
163 metavar="HG",
164 help="test using specified hg script rather than a "
164 help="test using specified hg script rather than a "
165 "temporary installation")
165 "temporary installation")
166 parser.add_option("-3", "--py3k-warnings", action="store_true",
166 parser.add_option("-3", "--py3k-warnings", action="store_true",
167 help="enable Py3k warnings on Python 2.6+")
167 help="enable Py3k warnings on Python 2.6+")
168 parser.add_option('--extra-config-opt', action="append",
168 parser.add_option('--extra-config-opt', action="append",
169 help='set the given config opt in the test hgrc')
169 help='set the given config opt in the test hgrc')
170
170
171 for option, default in defaults.items():
171 for option, default in defaults.items():
172 defaults[option] = int(os.environ.get(*default))
172 defaults[option] = int(os.environ.get(*default))
173 parser.set_defaults(**defaults)
173 parser.set_defaults(**defaults)
174 (options, args) = parser.parse_args()
174 (options, args) = parser.parse_args()
175
175
176 # jython is always pure
176 # jython is always pure
177 if 'java' in sys.platform or '__pypy__' in sys.modules:
177 if 'java' in sys.platform or '__pypy__' in sys.modules:
178 options.pure = True
178 options.pure = True
179
179
180 if options.with_hg:
180 if options.with_hg:
181 if not (os.path.isfile(options.with_hg) and
181 if not (os.path.isfile(options.with_hg) and
182 os.access(options.with_hg, os.X_OK)):
182 os.access(options.with_hg, os.X_OK)):
183 parser.error('--with-hg must specify an executable hg script')
183 parser.error('--with-hg must specify an executable hg script')
184 if not os.path.basename(options.with_hg) == 'hg':
184 if not os.path.basename(options.with_hg) == 'hg':
185 sys.stderr.write('warning: --with-hg should specify an hg script')
185 sys.stderr.write('warning: --with-hg should specify an hg script')
186 if options.local:
186 if options.local:
187 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
187 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
188 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
188 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
189 if not os.access(hgbin, os.X_OK):
189 if not os.access(hgbin, os.X_OK):
190 parser.error('--local specified, but %r not found or not executable'
190 parser.error('--local specified, but %r not found or not executable'
191 % hgbin)
191 % hgbin)
192 options.with_hg = hgbin
192 options.with_hg = hgbin
193
193
194 options.anycoverage = options.cover or options.annotate
194 options.anycoverage = options.cover or options.annotate
195 if options.anycoverage:
195 if options.anycoverage:
196 try:
196 try:
197 import coverage
197 import coverage
198 covver = version.StrictVersion(coverage.__version__).version
198 covver = version.StrictVersion(coverage.__version__).version
199 if covver < (3, 3):
199 if covver < (3, 3):
200 parser.error('coverage options require coverage 3.3 or later')
200 parser.error('coverage options require coverage 3.3 or later')
201 except ImportError:
201 except ImportError:
202 parser.error('coverage options now require the coverage package')
202 parser.error('coverage options now require the coverage package')
203
203
204 if options.anycoverage and options.local:
204 if options.anycoverage and options.local:
205 # this needs some path mangling somewhere, I guess
205 # this needs some path mangling somewhere, I guess
206 parser.error("sorry, coverage options do not work when --local "
206 parser.error("sorry, coverage options do not work when --local "
207 "is specified")
207 "is specified")
208
208
209 global vlog
209 global vlog
210 if options.verbose:
210 if options.verbose:
211 if options.jobs > 1 or options.child is not None:
211 if options.jobs > 1 or options.child is not None:
212 pid = "[%d]" % os.getpid()
212 pid = "[%d]" % os.getpid()
213 else:
213 else:
214 pid = None
214 pid = None
215 def vlog(*msg):
215 def vlog(*msg):
216 iolock.acquire()
216 iolock.acquire()
217 if pid:
217 if pid:
218 print pid,
218 print pid,
219 for m in msg:
219 for m in msg:
220 print m,
220 print m,
221 print
221 print
222 sys.stdout.flush()
222 sys.stdout.flush()
223 iolock.release()
223 iolock.release()
224 else:
224 else:
225 vlog = lambda *msg: None
225 vlog = lambda *msg: None
226
226
227 if options.tmpdir:
227 if options.tmpdir:
228 options.tmpdir = os.path.expanduser(options.tmpdir)
228 options.tmpdir = os.path.expanduser(options.tmpdir)
229
229
230 if options.jobs < 1:
230 if options.jobs < 1:
231 parser.error('--jobs must be positive')
231 parser.error('--jobs must be positive')
232 if options.interactive and options.jobs > 1:
232 if options.interactive and options.jobs > 1:
233 print '(--interactive overrides --jobs)'
233 print '(--interactive overrides --jobs)'
234 options.jobs = 1
234 options.jobs = 1
235 if options.interactive and options.debug:
235 if options.interactive and options.debug:
236 parser.error("-i/--interactive and -d/--debug are incompatible")
236 parser.error("-i/--interactive and -d/--debug are incompatible")
237 if options.debug:
237 if options.debug:
238 if options.timeout != defaults['timeout']:
238 if options.timeout != defaults['timeout']:
239 sys.stderr.write(
239 sys.stderr.write(
240 'warning: --timeout option ignored with --debug\n')
240 'warning: --timeout option ignored with --debug\n')
241 options.timeout = 0
241 options.timeout = 0
242 if options.py3k_warnings:
242 if options.py3k_warnings:
243 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
243 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
244 parser.error('--py3k-warnings can only be used on Python 2.6+')
244 parser.error('--py3k-warnings can only be used on Python 2.6+')
245 if options.blacklist:
245 if options.blacklist:
246 blacklist = dict()
246 blacklist = dict()
247 for filename in options.blacklist:
247 for filename in options.blacklist:
248 try:
248 try:
249 path = os.path.expanduser(os.path.expandvars(filename))
249 path = os.path.expanduser(os.path.expandvars(filename))
250 f = open(path, "r")
250 f = open(path, "r")
251 except IOError, err:
251 except IOError, err:
252 if err.errno != errno.ENOENT:
252 if err.errno != errno.ENOENT:
253 raise
253 raise
254 print "warning: no such blacklist file: %s" % filename
254 print "warning: no such blacklist file: %s" % filename
255 continue
255 continue
256
256
257 for line in f.readlines():
257 for line in f.readlines():
258 line = line.split('#', 1)[0].strip()
258 line = line.split('#', 1)[0].strip()
259 if line:
259 if line:
260 blacklist[line] = filename
260 blacklist[line] = filename
261
261
262 f.close()
262 f.close()
263
263
264 options.blacklist = blacklist
264 options.blacklist = blacklist
265
265
266 return (options, args)
266 return (options, args)
267
267
268 def rename(src, dst):
268 def rename(src, dst):
269 """Like os.rename(), trade atomicity and opened files friendliness
269 """Like os.rename(), trade atomicity and opened files friendliness
270 for existing destination support.
270 for existing destination support.
271 """
271 """
272 shutil.copy(src, dst)
272 shutil.copy(src, dst)
273 os.remove(src)
273 os.remove(src)
274
274
275 def splitnewlines(text):
275 def splitnewlines(text):
276 '''like str.splitlines, but only split on newlines.
276 '''like str.splitlines, but only split on newlines.
277 keep line endings.'''
277 keep line endings.'''
278 i = 0
278 i = 0
279 lines = []
279 lines = []
280 while True:
280 while True:
281 n = text.find('\n', i)
281 n = text.find('\n', i)
282 if n == -1:
282 if n == -1:
283 last = text[i:]
283 last = text[i:]
284 if last:
284 if last:
285 lines.append(last)
285 lines.append(last)
286 return lines
286 return lines
287 lines.append(text[i:n + 1])
287 lines.append(text[i:n + 1])
288 i = n + 1
288 i = n + 1
289
289
290 def parsehghaveoutput(lines):
290 def parsehghaveoutput(lines):
291 '''Parse hghave log lines.
291 '''Parse hghave log lines.
292 Return tuple of lists (missing, failed):
292 Return tuple of lists (missing, failed):
293 * the missing/unknown features
293 * the missing/unknown features
294 * the features for which existence check failed'''
294 * the features for which existence check failed'''
295 missing = []
295 missing = []
296 failed = []
296 failed = []
297 for line in lines:
297 for line in lines:
298 if line.startswith(SKIPPED_PREFIX):
298 if line.startswith(SKIPPED_PREFIX):
299 line = line.splitlines()[0]
299 line = line.splitlines()[0]
300 missing.append(line[len(SKIPPED_PREFIX):])
300 missing.append(line[len(SKIPPED_PREFIX):])
301 elif line.startswith(FAILED_PREFIX):
301 elif line.startswith(FAILED_PREFIX):
302 line = line.splitlines()[0]
302 line = line.splitlines()[0]
303 failed.append(line[len(FAILED_PREFIX):])
303 failed.append(line[len(FAILED_PREFIX):])
304
304
305 return missing, failed
305 return missing, failed
306
306
307 def showdiff(expected, output, ref, err):
307 def showdiff(expected, output, ref, err):
308 print
308 print
309 for line in difflib.unified_diff(expected, output, ref, err):
309 for line in difflib.unified_diff(expected, output, ref, err):
310 sys.stdout.write(line)
310 sys.stdout.write(line)
311
311
312 def findprogram(program):
312 def findprogram(program):
313 """Search PATH for a executable program"""
313 """Search PATH for a executable program"""
314 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
314 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
315 name = os.path.join(p, program)
315 name = os.path.join(p, program)
316 if os.access(name, os.X_OK):
316 if os.access(name, os.X_OK):
317 return name
317 return name
318 return None
318 return None
319
319
320 def checktools():
320 def checktools():
321 # Before we go any further, check for pre-requisite tools
321 # Before we go any further, check for pre-requisite tools
322 # stuff from coreutils (cat, rm, etc) are not tested
322 # stuff from coreutils (cat, rm, etc) are not tested
323 for p in requiredtools:
323 for p in requiredtools:
324 if os.name == 'nt':
324 if os.name == 'nt':
325 p += '.exe'
325 p += '.exe'
326 found = findprogram(p)
326 found = findprogram(p)
327 if found:
327 if found:
328 vlog("# Found prerequisite", p, "at", found)
328 vlog("# Found prerequisite", p, "at", found)
329 else:
329 else:
330 print "WARNING: Did not find prerequisite tool: "+p
330 print "WARNING: Did not find prerequisite tool: "+p
331
331
332 def killdaemons():
332 def killdaemons():
333 # Kill off any leftover daemon processes
333 # Kill off any leftover daemon processes
334 try:
334 try:
335 fp = open(DAEMON_PIDS)
335 fp = open(DAEMON_PIDS)
336 for line in fp:
336 for line in fp:
337 try:
337 try:
338 pid = int(line)
338 pid = int(line)
339 except ValueError:
339 except ValueError:
340 continue
340 continue
341 try:
341 try:
342 os.kill(pid, 0)
342 os.kill(pid, 0)
343 vlog('# Killing daemon process %d' % pid)
343 vlog('# Killing daemon process %d' % pid)
344 os.kill(pid, signal.SIGTERM)
344 os.kill(pid, signal.SIGTERM)
345 time.sleep(0.25)
345 time.sleep(0.25)
346 os.kill(pid, 0)
346 os.kill(pid, 0)
347 vlog('# Daemon process %d is stuck - really killing it' % pid)
347 vlog('# Daemon process %d is stuck - really killing it' % pid)
348 os.kill(pid, signal.SIGKILL)
348 os.kill(pid, signal.SIGKILL)
349 except OSError, err:
349 except OSError, err:
350 if err.errno != errno.ESRCH:
350 if err.errno != errno.ESRCH:
351 raise
351 raise
352 fp.close()
352 fp.close()
353 os.unlink(DAEMON_PIDS)
353 os.unlink(DAEMON_PIDS)
354 except IOError:
354 except IOError:
355 pass
355 pass
356
356
357 def cleanup(options):
357 def cleanup(options):
358 if not options.keep_tmpdir:
358 if not options.keep_tmpdir:
359 vlog("# Cleaning up HGTMP", HGTMP)
359 vlog("# Cleaning up HGTMP", HGTMP)
360 shutil.rmtree(HGTMP, True)
360 shutil.rmtree(HGTMP, True)
361
361
362 def usecorrectpython():
362 def usecorrectpython():
363 # some tests run python interpreter. they must use same
363 # some tests run python interpreter. they must use same
364 # interpreter we use or bad things will happen.
364 # interpreter we use or bad things will happen.
365 exedir, exename = os.path.split(sys.executable)
365 exedir, exename = os.path.split(sys.executable)
366 if exename == 'python':
366 if exename == 'python':
367 path = findprogram('python')
367 path = findprogram('python')
368 if os.path.dirname(path) == exedir:
368 if os.path.dirname(path) == exedir:
369 return
369 return
370 vlog('# Making python executable in test path use correct Python')
370 vlog('# Making python executable in test path use correct Python')
371 mypython = os.path.join(BINDIR, 'python')
371 mypython = os.path.join(BINDIR, 'python')
372 try:
372 try:
373 os.symlink(sys.executable, mypython)
373 os.symlink(sys.executable, mypython)
374 except AttributeError:
374 except AttributeError:
375 # windows fallback
375 # windows fallback
376 shutil.copyfile(sys.executable, mypython)
376 shutil.copyfile(sys.executable, mypython)
377 shutil.copymode(sys.executable, mypython)
377 shutil.copymode(sys.executable, mypython)
378
378
379 def installhg(options):
379 def installhg(options):
380 vlog("# Performing temporary installation of HG")
380 vlog("# Performing temporary installation of HG")
381 installerrs = os.path.join("tests", "install.err")
381 installerrs = os.path.join("tests", "install.err")
382 pure = options.pure and "--pure" or ""
382 pure = options.pure and "--pure" or ""
383
383
384 # Run installer in hg root
384 # Run installer in hg root
385 script = os.path.realpath(sys.argv[0])
385 script = os.path.realpath(sys.argv[0])
386 hgroot = os.path.dirname(os.path.dirname(script))
386 hgroot = os.path.dirname(os.path.dirname(script))
387 os.chdir(hgroot)
387 os.chdir(hgroot)
388 nohome = '--home=""'
388 nohome = '--home=""'
389 if os.name == 'nt':
389 if os.name == 'nt':
390 # The --home="" trick works only on OS where os.sep == '/'
390 # The --home="" trick works only on OS where os.sep == '/'
391 # because of a distutils convert_path() fast-path. Avoid it at
391 # because of a distutils convert_path() fast-path. Avoid it at
392 # least on Windows for now, deal with .pydistutils.cfg bugs
392 # least on Windows for now, deal with .pydistutils.cfg bugs
393 # when they happen.
393 # when they happen.
394 nohome = ''
394 nohome = ''
395 cmd = ('%s setup.py %s clean --all'
395 cmd = ('%s setup.py %s clean --all'
396 ' build --build-base="%s"'
396 ' build --build-base="%s"'
397 ' install --force --prefix="%s" --install-lib="%s"'
397 ' install --force --prefix="%s" --install-lib="%s"'
398 ' --install-scripts="%s" %s >%s 2>&1'
398 ' --install-scripts="%s" %s >%s 2>&1'
399 % (sys.executable, pure, os.path.join(HGTMP, "build"),
399 % (sys.executable, pure, os.path.join(HGTMP, "build"),
400 INST, PYTHONDIR, BINDIR, nohome, installerrs))
400 INST, PYTHONDIR, BINDIR, nohome, installerrs))
401 vlog("# Running", cmd)
401 vlog("# Running", cmd)
402 if os.system(cmd) == 0:
402 if os.system(cmd) == 0:
403 if not options.verbose:
403 if not options.verbose:
404 os.remove(installerrs)
404 os.remove(installerrs)
405 else:
405 else:
406 f = open(installerrs)
406 f = open(installerrs)
407 for line in f:
407 for line in f:
408 print line,
408 print line,
409 f.close()
409 f.close()
410 sys.exit(1)
410 sys.exit(1)
411 os.chdir(TESTDIR)
411 os.chdir(TESTDIR)
412
412
413 usecorrectpython()
413 usecorrectpython()
414
414
415 vlog("# Installing dummy diffstat")
415 vlog("# Installing dummy diffstat")
416 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
416 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
417 f.write('#!' + sys.executable + '\n'
417 f.write('#!' + sys.executable + '\n'
418 'import sys\n'
418 'import sys\n'
419 'files = 0\n'
419 'files = 0\n'
420 'for line in sys.stdin:\n'
420 'for line in sys.stdin:\n'
421 ' if line.startswith("diff "):\n'
421 ' if line.startswith("diff "):\n'
422 ' files += 1\n'
422 ' files += 1\n'
423 'sys.stdout.write("files patched: %d\\n" % files)\n')
423 'sys.stdout.write("files patched: %d\\n" % files)\n')
424 f.close()
424 f.close()
425 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
425 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
426
426
427 if options.py3k_warnings and not options.anycoverage:
427 if options.py3k_warnings and not options.anycoverage:
428 vlog("# Updating hg command to enable Py3k Warnings switch")
428 vlog("# Updating hg command to enable Py3k Warnings switch")
429 f = open(os.path.join(BINDIR, 'hg'), 'r')
429 f = open(os.path.join(BINDIR, 'hg'), 'r')
430 lines = [line.rstrip() for line in f]
430 lines = [line.rstrip() for line in f]
431 lines[0] += ' -3'
431 lines[0] += ' -3'
432 f.close()
432 f.close()
433 f = open(os.path.join(BINDIR, 'hg'), 'w')
433 f = open(os.path.join(BINDIR, 'hg'), 'w')
434 for line in lines:
434 for line in lines:
435 f.write(line + '\n')
435 f.write(line + '\n')
436 f.close()
436 f.close()
437
437
438 if options.anycoverage:
438 if options.anycoverage:
439 custom = os.path.join(TESTDIR, 'sitecustomize.py')
439 custom = os.path.join(TESTDIR, 'sitecustomize.py')
440 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
440 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
441 vlog('# Installing coverage trigger to %s' % target)
441 vlog('# Installing coverage trigger to %s' % target)
442 shutil.copyfile(custom, target)
442 shutil.copyfile(custom, target)
443 rc = os.path.join(TESTDIR, '.coveragerc')
443 rc = os.path.join(TESTDIR, '.coveragerc')
444 vlog('# Installing coverage rc to %s' % rc)
444 vlog('# Installing coverage rc to %s' % rc)
445 os.environ['COVERAGE_PROCESS_START'] = rc
445 os.environ['COVERAGE_PROCESS_START'] = rc
446 fn = os.path.join(INST, '..', '.coverage')
446 fn = os.path.join(INST, '..', '.coverage')
447 os.environ['COVERAGE_FILE'] = fn
447 os.environ['COVERAGE_FILE'] = fn
448
448
449 def outputcoverage(options):
449 def outputcoverage(options):
450
450
451 vlog('# Producing coverage report')
451 vlog('# Producing coverage report')
452 os.chdir(PYTHONDIR)
452 os.chdir(PYTHONDIR)
453
453
454 def covrun(*args):
454 def covrun(*args):
455 cmd = 'coverage %s' % ' '.join(args)
455 cmd = 'coverage %s' % ' '.join(args)
456 vlog('# Running: %s' % cmd)
456 vlog('# Running: %s' % cmd)
457 os.system(cmd)
457 os.system(cmd)
458
458
459 if options.child:
459 if options.child:
460 return
460 return
461
461
462 covrun('-c')
462 covrun('-c')
463 omit = ','.join([BINDIR, TESTDIR])
463 omit = ','.join([BINDIR, TESTDIR])
464 covrun('-i', '-r', '"--omit=%s"' % omit) # report
464 covrun('-i', '-r', '"--omit=%s"' % omit) # report
465 if options.annotate:
465 if options.annotate:
466 adir = os.path.join(TESTDIR, 'annotated')
466 adir = os.path.join(TESTDIR, 'annotated')
467 if not os.path.isdir(adir):
467 if not os.path.isdir(adir):
468 os.mkdir(adir)
468 os.mkdir(adir)
469 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
469 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
470
470
471 def pytest(test, wd, options, replacements):
471 def pytest(test, wd, options, replacements):
472 py3kswitch = options.py3k_warnings and ' -3' or ''
472 py3kswitch = options.py3k_warnings and ' -3' or ''
473 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
473 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
474 vlog("# Running", cmd)
474 vlog("# Running", cmd)
475 return run(cmd, wd, options, replacements)
475 return run(cmd, wd, options, replacements)
476
476
477 def shtest(test, wd, options, replacements):
477 def shtest(test, wd, options, replacements):
478 cmd = '"%s"' % test
478 cmd = '"%s"' % test
479 vlog("# Running", cmd)
479 vlog("# Running", cmd)
480 return run(cmd, wd, options, replacements)
480 return run(cmd, wd, options, replacements)
481
481
482 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
482 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
483 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
483 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
484 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
484 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
485 escapemap.update({'\\': '\\\\', '\r': r'\r'})
485 escapemap.update({'\\': '\\\\', '\r': r'\r'})
486 def escapef(m):
486 def escapef(m):
487 return escapemap[m.group(0)]
487 return escapemap[m.group(0)]
488 def stringescape(s):
488 def stringescape(s):
489 return escapesub(escapef, s)
489 return escapesub(escapef, s)
490
490
491 def tsttest(test, wd, options, replacements):
491 def tsttest(test, wd, options, replacements):
492 t = open(test)
492 t = open(test)
493 out = []
493 out = []
494 script = []
494 script = []
495 salt = "SALT" + str(time.time())
495 salt = "SALT" + str(time.time())
496
496
497 pos = prepos = -1
497 pos = prepos = -1
498 after = {}
498 after = {}
499 expected = {}
499 expected = {}
500 for n, l in enumerate(t):
500 for n, l in enumerate(t):
501 if not l.endswith('\n'):
501 if not l.endswith('\n'):
502 l += '\n'
502 l += '\n'
503 if l.startswith(' $ '): # commands
503 if l.startswith(' $ '): # commands
504 after.setdefault(pos, []).append(l)
504 after.setdefault(pos, []).append(l)
505 prepos = pos
505 prepos = pos
506 pos = n
506 pos = n
507 script.append('echo %s %s $?\n' % (salt, n))
507 script.append('echo %s %s $?\n' % (salt, n))
508 script.append(l[4:])
508 script.append(l[4:])
509 elif l.startswith(' > '): # continuations
509 elif l.startswith(' > '): # continuations
510 after.setdefault(prepos, []).append(l)
510 after.setdefault(prepos, []).append(l)
511 script.append(l[4:])
511 script.append(l[4:])
512 elif l.startswith(' '): # results
512 elif l.startswith(' '): # results
513 # queue up a list of expected results
513 # queue up a list of expected results
514 expected.setdefault(pos, []).append(l[2:])
514 expected.setdefault(pos, []).append(l[2:])
515 else:
515 else:
516 # non-command/result - queue up for merged output
516 # non-command/result - queue up for merged output
517 after.setdefault(pos, []).append(l)
517 after.setdefault(pos, []).append(l)
518
518
519 t.close()
519 t.close()
520
520
521 script.append('echo %s %s $?\n' % (salt, n + 1))
521 script.append('echo %s %s $?\n' % (salt, n + 1))
522
522
523 fd, name = tempfile.mkstemp(suffix='hg-tst')
523 fd, name = tempfile.mkstemp(suffix='hg-tst')
524
524
525 try:
525 try:
526 for l in script:
526 for l in script:
527 os.write(fd, l)
527 os.write(fd, l)
528 os.close(fd)
528 os.close(fd)
529
529
530 cmd = '/bin/sh "%s"' % name
530 cmd = '/bin/sh "%s"' % name
531 vlog("# Running", cmd)
531 vlog("# Running", cmd)
532 exitcode, output = run(cmd, wd, options, replacements)
532 exitcode, output = run(cmd, wd, options, replacements)
533 # do not merge output if skipped, return hghave message instead
533 # do not merge output if skipped, return hghave message instead
534 # similarly, with --debug, output is None
534 # similarly, with --debug, output is None
535 if exitcode == SKIPPED_STATUS or output is None:
535 if exitcode == SKIPPED_STATUS or output is None:
536 return exitcode, output
536 return exitcode, output
537 finally:
537 finally:
538 os.remove(name)
538 os.remove(name)
539
539
540 def rematch(el, l):
540 def rematch(el, l):
541 try:
541 try:
542 # ensure that the regex matches to the end of the string
542 # ensure that the regex matches to the end of the string
543 return re.match(el + r'\Z', l)
543 return re.match(el + r'\Z', l)
544 except re.error:
544 except re.error:
545 # el is an invalid regex
545 # el is an invalid regex
546 return False
546 return False
547
547
548 def globmatch(el, l):
548 def globmatch(el, l):
549 # The only supported special characters are * and ?. Escaping is
549 # The only supported special characters are * and ?. Escaping is
550 # supported.
550 # supported.
551 i, n = 0, len(el)
551 i, n = 0, len(el)
552 res = ''
552 res = ''
553 while i < n:
553 while i < n:
554 c = el[i]
554 c = el[i]
555 i += 1
555 i += 1
556 if c == '\\' and el[i] in '*?\\':
556 if c == '\\' and el[i] in '*?\\':
557 res += el[i - 1:i + 1]
557 res += el[i - 1:i + 1]
558 i += 1
558 i += 1
559 elif c == '*':
559 elif c == '*':
560 res += '.*'
560 res += '.*'
561 elif c == '?':
561 elif c == '?':
562 res += '.'
562 res += '.'
563 else:
563 else:
564 res += re.escape(c)
564 res += re.escape(c)
565 return rematch(res, l)
565 return rematch(res, l)
566
566
567 pos = -1
567 pos = -1
568 postout = []
568 postout = []
569 ret = 0
569 ret = 0
570 for n, l in enumerate(output):
570 for n, l in enumerate(output):
571 lout, lcmd = l, None
571 lout, lcmd = l, None
572 if salt in l:
572 if salt in l:
573 lout, lcmd = l.split(salt, 1)
573 lout, lcmd = l.split(salt, 1)
574
574
575 if lout:
575 if lout:
576 if lcmd:
576 if lcmd:
577 lout += ' (no-eol)\n'
577 lout += ' (no-eol)\n'
578
578
579 el = None
579 el = None
580 if pos in expected and expected[pos]:
580 if pos in expected and expected[pos]:
581 el = expected[pos].pop(0)
581 el = expected[pos].pop(0)
582
582
583 if el == lout: # perfect match (fast)
583 if el == lout: # perfect match (fast)
584 postout.append(" " + lout)
584 postout.append(" " + lout)
585 elif (el and
585 elif (el and
586 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
586 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
587 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
587 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
588 or el.endswith(" (esc)\n") and
588 or el.endswith(" (esc)\n") and
589 el.decode('string-escape') == l)):
589 el.decode('string-escape') == l)):
590 postout.append(" " + el) # fallback regex/glob/esc match
590 postout.append(" " + el) # fallback regex/glob/esc match
591 else:
591 else:
592 if needescape(lout):
592 if needescape(lout):
593 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
593 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
594 postout.append(" " + lout) # let diff deal with it
594 postout.append(" " + lout) # let diff deal with it
595
595
596 if lcmd:
596 if lcmd:
597 # add on last return code
597 # add on last return code
598 ret = int(lcmd.split()[1])
598 ret = int(lcmd.split()[1])
599 if ret != 0:
599 if ret != 0:
600 postout.append(" [%s]\n" % ret)
600 postout.append(" [%s]\n" % ret)
601 if pos in after:
601 if pos in after:
602 postout += after.pop(pos)
602 postout += after.pop(pos)
603 pos = int(lcmd.split()[0])
603 pos = int(lcmd.split()[0])
604
604
605 if pos in after:
605 if pos in after:
606 postout += after.pop(pos)
606 postout += after.pop(pos)
607
607
608 return exitcode, postout
608 return exitcode, postout
609
609
610 wifexited = getattr(os, "WIFEXITED", lambda x: False)
610 wifexited = getattr(os, "WIFEXITED", lambda x: False)
611 def run(cmd, wd, options, replacements):
611 def run(cmd, wd, options, replacements):
612 """Run command in a sub-process, capturing the output (stdout and stderr).
612 """Run command in a sub-process, capturing the output (stdout and stderr).
613 Return a tuple (exitcode, output). output is None in debug mode."""
613 Return a tuple (exitcode, output). output is None in debug mode."""
614 # TODO: Use subprocess.Popen if we're running on Python 2.4
614 # TODO: Use subprocess.Popen if we're running on Python 2.4
615 if options.debug:
615 if options.debug:
616 proc = subprocess.Popen(cmd, shell=True)
616 proc = subprocess.Popen(cmd, shell=True)
617 ret = proc.wait()
617 ret = proc.wait()
618 return (ret, None)
618 return (ret, None)
619
619
620 if os.name == 'nt' or sys.platform.startswith('java'):
620 if os.name == 'nt' or sys.platform.startswith('java'):
621 tochild, fromchild = os.popen4(cmd)
621 tochild, fromchild = os.popen4(cmd)
622 tochild.close()
622 tochild.close()
623 output = fromchild.read()
623 output = fromchild.read()
624 ret = fromchild.close()
624 ret = fromchild.close()
625 if ret is None:
625 if ret is None:
626 ret = 0
626 ret = 0
627 else:
627 else:
628 proc = Popen4(cmd, wd, options.timeout)
628 proc = Popen4(cmd, wd, options.timeout)
629 def cleanup():
629 def cleanup():
630 try:
630 try:
631 proc.terminate()
631 proc.terminate()
632 except OSError:
632 except OSError:
633 pass
633 pass
634 ret = proc.wait()
634 ret = proc.wait()
635 if ret == 0:
635 if ret == 0:
636 ret = signal.SIGTERM << 8
636 ret = signal.SIGTERM << 8
637 killdaemons()
637 killdaemons()
638 return ret
638 return ret
639
639
640 output = ''
640 output = ''
641 proc.tochild.close()
641 proc.tochild.close()
642
642
643 try:
643 try:
644 output = proc.fromchild.read()
644 output = proc.fromchild.read()
645 except KeyboardInterrupt:
645 except KeyboardInterrupt:
646 vlog('# Handling keyboard interrupt')
646 vlog('# Handling keyboard interrupt')
647 cleanup()
647 cleanup()
648 raise
648 raise
649
649
650 ret = proc.wait()
650 ret = proc.wait()
651 if wifexited(ret):
651 if wifexited(ret):
652 ret = os.WEXITSTATUS(ret)
652 ret = os.WEXITSTATUS(ret)
653
653
654 if proc.timeout:
654 if proc.timeout:
655 ret = 'timeout'
655 ret = 'timeout'
656
656
657 if ret:
657 if ret:
658 killdaemons()
658 killdaemons()
659
659
660 for s, r in replacements:
660 for s, r in replacements:
661 output = re.sub(s, r, output)
661 output = re.sub(s, r, output)
662 return ret, splitnewlines(output)
662 return ret, splitnewlines(output)
663
663
664 def runone(options, test):
664 def runone(options, test):
665 '''tristate output:
665 '''tristate output:
666 None -> skipped
666 None -> skipped
667 True -> passed
667 True -> passed
668 False -> failed'''
668 False -> failed'''
669
669
670 global results, resultslock, iolock
670 global results, resultslock, iolock
671
671
672 testpath = os.path.join(TESTDIR, test)
672 testpath = os.path.join(TESTDIR, test)
673
673
674 def result(l, e):
674 def result(l, e):
675 resultslock.acquire()
675 resultslock.acquire()
676 results[l].append(e)
676 results[l].append(e)
677 resultslock.release()
677 resultslock.release()
678
678
679 def skip(msg):
679 def skip(msg):
680 if not options.verbose:
680 if not options.verbose:
681 result('s', (test, msg))
681 result('s', (test, msg))
682 else:
682 else:
683 iolock.acquire()
683 iolock.acquire()
684 print "\nSkipping %s: %s" % (testpath, msg)
684 print "\nSkipping %s: %s" % (testpath, msg)
685 iolock.release()
685 iolock.release()
686 return None
686 return None
687
687
688 def fail(msg, ret):
688 def fail(msg, ret):
689 if not options.nodiff:
689 if not options.nodiff:
690 iolock.acquire()
690 iolock.acquire()
691 print "\nERROR: %s %s" % (testpath, msg)
691 print "\nERROR: %s %s" % (testpath, msg)
692 iolock.release()
692 iolock.release()
693 if (not ret and options.interactive
693 if (not ret and options.interactive
694 and os.path.exists(testpath + ".err")):
694 and os.path.exists(testpath + ".err")):
695 iolock.acquire()
695 iolock.acquire()
696 print "Accept this change? [n] ",
696 print "Accept this change? [n] ",
697 answer = sys.stdin.readline().strip()
697 answer = sys.stdin.readline().strip()
698 iolock.release()
698 iolock.release()
699 if answer.lower() in "y yes".split():
699 if answer.lower() in "y yes".split():
700 if test.endswith(".t"):
700 if test.endswith(".t"):
701 rename(testpath + ".err", testpath)
701 rename(testpath + ".err", testpath)
702 else:
702 else:
703 rename(testpath + ".err", testpath + ".out")
703 rename(testpath + ".err", testpath + ".out")
704 return
704 return
705 result('f', (test, msg))
705 result('f', (test, msg))
706
706
707 def success():
707 def success():
708 result('p', test)
708 result('p', test)
709
709
710 def ignore(msg):
710 def ignore(msg):
711 result('i', (test, msg))
711 result('i', (test, msg))
712
712
713 if (test.startswith("test-") and '~' not in test and
713 if (test.startswith("test-") and '~' not in test and
714 ('.' not in test or test.endswith('.py') or
714 ('.' not in test or test.endswith('.py') or
715 test.endswith('.bat') or test.endswith('.t'))):
715 test.endswith('.bat') or test.endswith('.t'))):
716 if not os.path.exists(test):
716 if not os.path.exists(test):
717 skip("doesn't exist")
717 skip("doesn't exist")
718 return None
718 return None
719 else:
719 else:
720 return None # not a supported test, don't record
720 return None # not a supported test, don't record
721
721
722 if options.blacklist:
722 if options.blacklist:
723 filename = options.blacklist.get(test)
723 filename = options.blacklist.get(test)
724 if filename is not None:
724 if filename is not None:
725 skip("blacklisted")
725 skip("blacklisted")
726 return None
726 return None
727
727
728 if options.retest and not os.path.exists(test + ".err"):
728 if options.retest and not os.path.exists(test + ".err"):
729 ignore("not retesting")
729 ignore("not retesting")
730 return None
730 return None
731
731
732 if options.keywords:
732 if options.keywords:
733 fp = open(test)
733 fp = open(test)
734 t = fp.read().lower() + test.lower()
734 t = fp.read().lower() + test.lower()
735 fp.close()
735 fp.close()
736 for k in options.keywords.lower().split():
736 for k in options.keywords.lower().split():
737 if k in t:
737 if k in t:
738 break
738 break
739 else:
739 else:
740 ignore("doesn't match keyword")
740 ignore("doesn't match keyword")
741 return None
741 return None
742
742
743 vlog("# Test", test)
743 vlog("# Test", test)
744
744
745 # create a fresh hgrc
745 # create a fresh hgrc
746 hgrc = open(HGRCPATH, 'w+')
746 hgrc = open(HGRCPATH, 'w+')
747 hgrc.write('[ui]\n')
747 hgrc.write('[ui]\n')
748 hgrc.write('slash = True\n')
748 hgrc.write('slash = True\n')
749 hgrc.write('[defaults]\n')
749 hgrc.write('[defaults]\n')
750 hgrc.write('backout = -d "0 0"\n')
750 hgrc.write('backout = -d "0 0"\n')
751 hgrc.write('commit = -d "0 0"\n')
751 hgrc.write('commit = -d "0 0"\n')
752 hgrc.write('tag = -d "0 0"\n')
752 hgrc.write('tag = -d "0 0"\n')
753 if options.inotify:
753 if options.inotify:
754 hgrc.write('[extensions]\n')
754 hgrc.write('[extensions]\n')
755 hgrc.write('inotify=\n')
755 hgrc.write('inotify=\n')
756 hgrc.write('[inotify]\n')
756 hgrc.write('[inotify]\n')
757 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
757 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
758 hgrc.write('appendpid=True\n')
758 hgrc.write('appendpid=True\n')
759 if options.extra_config_opt:
759 if options.extra_config_opt:
760 for opt in options.extra_config_opt:
760 for opt in options.extra_config_opt:
761 section, key = opt.split('.', 1)
761 section, key = opt.split('.', 1)
762 assert '=' in key, ('extra config opt %s must '
762 assert '=' in key, ('extra config opt %s must '
763 'have an = for assignment' % opt)
763 'have an = for assignment' % opt)
764 hgrc.write('[%s]\n%s\n' % (section, key))
764 hgrc.write('[%s]\n%s\n' % (section, key))
765 hgrc.close()
765 hgrc.close()
766
766
767 ref = os.path.join(TESTDIR, test+".out")
767 ref = os.path.join(TESTDIR, test+".out")
768 err = os.path.join(TESTDIR, test+".err")
768 err = os.path.join(TESTDIR, test+".err")
769 if os.path.exists(err):
769 if os.path.exists(err):
770 os.remove(err) # Remove any previous output files
770 os.remove(err) # Remove any previous output files
771 try:
771 try:
772 tf = open(testpath)
772 tf = open(testpath)
773 firstline = tf.readline().rstrip()
773 firstline = tf.readline().rstrip()
774 tf.close()
774 tf.close()
775 except:
775 except:
776 firstline = ''
776 firstline = ''
777 lctest = test.lower()
777 lctest = test.lower()
778
778
779 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
779 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
780 runner = pytest
780 runner = pytest
781 elif lctest.endswith('.t'):
781 elif lctest.endswith('.t'):
782 runner = tsttest
782 runner = tsttest
783 ref = testpath
783 ref = testpath
784 else:
784 else:
785 # do not try to run non-executable programs
785 # do not try to run non-executable programs
786 if not os.access(testpath, os.X_OK):
786 if not os.access(testpath, os.X_OK):
787 return skip("not executable")
787 return skip("not executable")
788 runner = shtest
788 runner = shtest
789
789
790 # Make a tmp subdirectory to work in
790 # Make a tmp subdirectory to work in
791 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
791 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
792 os.path.join(HGTMP, test)
792 os.path.join(HGTMP, test)
793
793
794 os.mkdir(testtmp)
794 os.mkdir(testtmp)
795 ret, out = runner(testpath, testtmp, options, [
795 ret, out = runner(testpath, testtmp, options, [
796 (re.escape(testtmp), '$TESTTMP'),
796 (re.escape(testtmp), '$TESTTMP'),
797 (r':%s\b' % options.port, ':$HGPORT'),
797 (r':%s\b' % options.port, ':$HGPORT'),
798 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
798 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
799 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
799 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
800 ])
800 ])
801 vlog("# Ret was:", ret)
801 vlog("# Ret was:", ret)
802
802
803 mark = '.'
803 mark = '.'
804
804
805 skipped = (ret == SKIPPED_STATUS)
805 skipped = (ret == SKIPPED_STATUS)
806
806
807 # If we're not in --debug mode and reference output file exists,
807 # If we're not in --debug mode and reference output file exists,
808 # check test output against it.
808 # check test output against it.
809 if options.debug:
809 if options.debug:
810 refout = None # to match "out is None"
810 refout = None # to match "out is None"
811 elif os.path.exists(ref):
811 elif os.path.exists(ref):
812 f = open(ref, "r")
812 f = open(ref, "r")
813 refout = splitnewlines(f.read())
813 refout = splitnewlines(f.read())
814 f.close()
814 f.close()
815 else:
815 else:
816 refout = []
816 refout = []
817
817
818 if (ret != 0 or out != refout) and not skipped and not options.debug:
818 if (ret != 0 or out != refout) and not skipped and not options.debug:
819 # Save errors to a file for diagnosis
819 # Save errors to a file for diagnosis
820 f = open(err, "wb")
820 f = open(err, "wb")
821 for line in out:
821 for line in out:
822 f.write(line)
822 f.write(line)
823 f.close()
823 f.close()
824
824
825 if skipped:
825 if skipped:
826 mark = 's'
826 mark = 's'
827 if out is None: # debug mode: nothing to parse
827 if out is None: # debug mode: nothing to parse
828 missing = ['unknown']
828 missing = ['unknown']
829 failed = None
829 failed = None
830 else:
830 else:
831 missing, failed = parsehghaveoutput(out)
831 missing, failed = parsehghaveoutput(out)
832 if not missing:
832 if not missing:
833 missing = ['irrelevant']
833 missing = ['irrelevant']
834 if failed:
834 if failed:
835 fail("hghave failed checking for %s" % failed[-1], ret)
835 fail("hghave failed checking for %s" % failed[-1], ret)
836 skipped = False
836 skipped = False
837 else:
837 else:
838 skip(missing[-1])
838 skip(missing[-1])
839 elif ret == 'timeout':
839 elif ret == 'timeout':
840 mark = 't'
840 mark = 't'
841 fail("timed out", ret)
841 fail("timed out", ret)
842 elif out != refout:
842 elif out != refout:
843 mark = '!'
843 mark = '!'
844 if not options.nodiff:
844 if not options.nodiff:
845 iolock.acquire()
845 iolock.acquire()
846 if options.view:
846 if options.view:
847 os.system("%s %s %s" % (options.view, ref, err))
847 os.system("%s %s %s" % (options.view, ref, err))
848 else:
848 else:
849 showdiff(refout, out, ref, err)
849 showdiff(refout, out, ref, err)
850 iolock.release()
850 iolock.release()
851 if ret:
851 if ret:
852 fail("output changed and returned error code %d" % ret, ret)
852 fail("output changed and returned error code %d" % ret, ret)
853 else:
853 else:
854 fail("output changed", ret)
854 fail("output changed", ret)
855 ret = 1
855 ret = 1
856 elif ret:
856 elif ret:
857 mark = '!'
857 mark = '!'
858 fail("returned error code %d" % ret, ret)
858 fail("returned error code %d" % ret, ret)
859 else:
859 else:
860 success()
860 success()
861
861
862 if not options.verbose:
862 if not options.verbose:
863 iolock.acquire()
863 iolock.acquire()
864 sys.stdout.write(mark)
864 sys.stdout.write(mark)
865 sys.stdout.flush()
865 sys.stdout.flush()
866 iolock.release()
866 iolock.release()
867
867
868 killdaemons()
868 killdaemons()
869
869
870 if not options.keep_tmpdir:
870 if not options.keep_tmpdir:
871 shutil.rmtree(testtmp, True)
871 shutil.rmtree(testtmp, True)
872 if skipped:
872 if skipped:
873 return None
873 return None
874 return ret == 0
874 return ret == 0
875
875
876 _hgpath = None
876 _hgpath = None
877
877
878 def _gethgpath():
878 def _gethgpath():
879 """Return the path to the mercurial package that is actually found by
879 """Return the path to the mercurial package that is actually found by
880 the current Python interpreter."""
880 the current Python interpreter."""
881 global _hgpath
881 global _hgpath
882 if _hgpath is not None:
882 if _hgpath is not None:
883 return _hgpath
883 return _hgpath
884
884
885 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
885 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
886 pipe = os.popen(cmd % PYTHON)
886 pipe = os.popen(cmd % PYTHON)
887 try:
887 try:
888 _hgpath = pipe.read().strip()
888 _hgpath = pipe.read().strip()
889 finally:
889 finally:
890 pipe.close()
890 pipe.close()
891 return _hgpath
891 return _hgpath
892
892
893 def _checkhglib(verb):
893 def _checkhglib(verb):
894 """Ensure that the 'mercurial' package imported by python is
894 """Ensure that the 'mercurial' package imported by python is
895 the one we expect it to be. If not, print a warning to stderr."""
895 the one we expect it to be. If not, print a warning to stderr."""
896 expecthg = os.path.join(PYTHONDIR, 'mercurial')
896 expecthg = os.path.join(PYTHONDIR, 'mercurial')
897 actualhg = _gethgpath()
897 actualhg = _gethgpath()
898 if actualhg != expecthg:
898 if actualhg != expecthg:
899 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
899 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
900 ' (expected %s)\n'
900 ' (expected %s)\n'
901 % (verb, actualhg, expecthg))
901 % (verb, actualhg, expecthg))
902
902
903 def runchildren(options, tests):
903 def runchildren(options, tests):
904 if INST:
904 if INST:
905 installhg(options)
905 installhg(options)
906 _checkhglib("Testing")
906 _checkhglib("Testing")
907
907
908 optcopy = dict(options.__dict__)
908 optcopy = dict(options.__dict__)
909 optcopy['jobs'] = 1
909 optcopy['jobs'] = 1
910 del optcopy['blacklist']
910 del optcopy['blacklist']
911 if optcopy['with_hg'] is None:
911 if optcopy['with_hg'] is None:
912 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
912 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
913 optcopy.pop('anycoverage', None)
913 optcopy.pop('anycoverage', None)
914
914
915 opts = []
915 opts = []
916 for opt, value in optcopy.iteritems():
916 for opt, value in optcopy.iteritems():
917 name = '--' + opt.replace('_', '-')
917 name = '--' + opt.replace('_', '-')
918 if value is True:
918 if value is True:
919 opts.append(name)
919 opts.append(name)
920 elif isinstance(value, list):
921 for v in value:
922 opts.append(name + '=' + str(v))
920 elif value is not None:
923 elif value is not None:
921 opts.append(name + '=' + str(value))
924 opts.append(name + '=' + str(value))
922
925
923 tests.reverse()
926 tests.reverse()
924 jobs = [[] for j in xrange(options.jobs)]
927 jobs = [[] for j in xrange(options.jobs)]
925 while tests:
928 while tests:
926 for job in jobs:
929 for job in jobs:
927 if not tests:
930 if not tests:
928 break
931 break
929 job.append(tests.pop())
932 job.append(tests.pop())
930 fps = {}
933 fps = {}
931
934
932 for j, job in enumerate(jobs):
935 for j, job in enumerate(jobs):
933 if not job:
936 if not job:
934 continue
937 continue
935 rfd, wfd = os.pipe()
938 rfd, wfd = os.pipe()
936 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
939 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
937 childtmp = os.path.join(HGTMP, 'child%d' % j)
940 childtmp = os.path.join(HGTMP, 'child%d' % j)
938 childopts += ['--tmpdir', childtmp]
941 childopts += ['--tmpdir', childtmp]
939 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
942 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
940 vlog(' '.join(cmdline))
943 vlog(' '.join(cmdline))
941 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
944 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
942 os.close(wfd)
945 os.close(wfd)
943 signal.signal(signal.SIGINT, signal.SIG_IGN)
946 signal.signal(signal.SIGINT, signal.SIG_IGN)
944 failures = 0
947 failures = 0
945 tested, skipped, failed = 0, 0, 0
948 tested, skipped, failed = 0, 0, 0
946 skips = []
949 skips = []
947 fails = []
950 fails = []
948 while fps:
951 while fps:
949 pid, status = os.wait()
952 pid, status = os.wait()
950 fp = fps.pop(pid)
953 fp = fps.pop(pid)
951 l = fp.read().splitlines()
954 l = fp.read().splitlines()
952 try:
955 try:
953 test, skip, fail = map(int, l[:3])
956 test, skip, fail = map(int, l[:3])
954 except ValueError:
957 except ValueError:
955 test, skip, fail = 0, 0, 0
958 test, skip, fail = 0, 0, 0
956 split = -fail or len(l)
959 split = -fail or len(l)
957 for s in l[3:split]:
960 for s in l[3:split]:
958 skips.append(s.split(" ", 1))
961 skips.append(s.split(" ", 1))
959 for s in l[split:]:
962 for s in l[split:]:
960 fails.append(s.split(" ", 1))
963 fails.append(s.split(" ", 1))
961 tested += test
964 tested += test
962 skipped += skip
965 skipped += skip
963 failed += fail
966 failed += fail
964 vlog('pid %d exited, status %d' % (pid, status))
967 vlog('pid %d exited, status %d' % (pid, status))
965 failures |= status
968 failures |= status
966 print
969 print
967 if not options.noskips:
970 if not options.noskips:
968 for s in skips:
971 for s in skips:
969 print "Skipped %s: %s" % (s[0], s[1])
972 print "Skipped %s: %s" % (s[0], s[1])
970 for s in fails:
973 for s in fails:
971 print "Failed %s: %s" % (s[0], s[1])
974 print "Failed %s: %s" % (s[0], s[1])
972
975
973 _checkhglib("Tested")
976 _checkhglib("Tested")
974 print "# Ran %d tests, %d skipped, %d failed." % (
977 print "# Ran %d tests, %d skipped, %d failed." % (
975 tested, skipped, failed)
978 tested, skipped, failed)
976
979
977 if options.anycoverage:
980 if options.anycoverage:
978 outputcoverage(options)
981 outputcoverage(options)
979 sys.exit(failures != 0)
982 sys.exit(failures != 0)
980
983
981 results = dict(p=[], f=[], s=[], i=[])
984 results = dict(p=[], f=[], s=[], i=[])
982 resultslock = threading.Lock()
985 resultslock = threading.Lock()
983 iolock = threading.Lock()
986 iolock = threading.Lock()
984
987
985 def runqueue(options, tests, results):
988 def runqueue(options, tests, results):
986 for test in tests:
989 for test in tests:
987 ret = runone(options, test)
990 ret = runone(options, test)
988 if options.first and ret is not None and not ret:
991 if options.first and ret is not None and not ret:
989 break
992 break
990
993
991 def runtests(options, tests):
994 def runtests(options, tests):
992 global DAEMON_PIDS, HGRCPATH
995 global DAEMON_PIDS, HGRCPATH
993 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
996 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
994 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
997 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
995
998
996 try:
999 try:
997 if INST:
1000 if INST:
998 installhg(options)
1001 installhg(options)
999 _checkhglib("Testing")
1002 _checkhglib("Testing")
1000
1003
1001 if options.restart:
1004 if options.restart:
1002 orig = list(tests)
1005 orig = list(tests)
1003 while tests:
1006 while tests:
1004 if os.path.exists(tests[0] + ".err"):
1007 if os.path.exists(tests[0] + ".err"):
1005 break
1008 break
1006 tests.pop(0)
1009 tests.pop(0)
1007 if not tests:
1010 if not tests:
1008 print "running all tests"
1011 print "running all tests"
1009 tests = orig
1012 tests = orig
1010
1013
1011 runqueue(options, tests, results)
1014 runqueue(options, tests, results)
1012
1015
1013 failed = len(results['f'])
1016 failed = len(results['f'])
1014 tested = len(results['p']) + failed
1017 tested = len(results['p']) + failed
1015 skipped = len(results['s'])
1018 skipped = len(results['s'])
1016 ignored = len(results['i'])
1019 ignored = len(results['i'])
1017
1020
1018 if options.child:
1021 if options.child:
1019 fp = os.fdopen(options.child, 'w')
1022 fp = os.fdopen(options.child, 'w')
1020 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1023 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1021 for s in results['s']:
1024 for s in results['s']:
1022 fp.write("%s %s\n" % s)
1025 fp.write("%s %s\n" % s)
1023 for s in results['f']:
1026 for s in results['f']:
1024 fp.write("%s %s\n" % s)
1027 fp.write("%s %s\n" % s)
1025 fp.close()
1028 fp.close()
1026 else:
1029 else:
1027 print
1030 print
1028 for s in results['s']:
1031 for s in results['s']:
1029 print "Skipped %s: %s" % s
1032 print "Skipped %s: %s" % s
1030 for s in results['f']:
1033 for s in results['f']:
1031 print "Failed %s: %s" % s
1034 print "Failed %s: %s" % s
1032 _checkhglib("Tested")
1035 _checkhglib("Tested")
1033 print "# Ran %d tests, %d skipped, %d failed." % (
1036 print "# Ran %d tests, %d skipped, %d failed." % (
1034 tested, skipped + ignored, failed)
1037 tested, skipped + ignored, failed)
1035
1038
1036 if options.anycoverage:
1039 if options.anycoverage:
1037 outputcoverage(options)
1040 outputcoverage(options)
1038 except KeyboardInterrupt:
1041 except KeyboardInterrupt:
1039 failed = True
1042 failed = True
1040 print "\ninterrupted!"
1043 print "\ninterrupted!"
1041
1044
1042 if failed:
1045 if failed:
1043 sys.exit(1)
1046 sys.exit(1)
1044
1047
1045 def main():
1048 def main():
1046 (options, args) = parseargs()
1049 (options, args) = parseargs()
1047 if not options.child:
1050 if not options.child:
1048 os.umask(022)
1051 os.umask(022)
1049
1052
1050 checktools()
1053 checktools()
1051
1054
1052 if len(args) == 0:
1055 if len(args) == 0:
1053 args = os.listdir(".")
1056 args = os.listdir(".")
1054 args.sort()
1057 args.sort()
1055
1058
1056 tests = args
1059 tests = args
1057
1060
1058 # Reset some environment variables to well-known values so that
1061 # Reset some environment variables to well-known values so that
1059 # the tests produce repeatable output.
1062 # the tests produce repeatable output.
1060 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1063 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1061 os.environ['TZ'] = 'GMT'
1064 os.environ['TZ'] = 'GMT'
1062 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1065 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1063 os.environ['CDPATH'] = ''
1066 os.environ['CDPATH'] = ''
1064 os.environ['COLUMNS'] = '80'
1067 os.environ['COLUMNS'] = '80'
1065 os.environ['GREP_OPTIONS'] = ''
1068 os.environ['GREP_OPTIONS'] = ''
1066 os.environ['http_proxy'] = ''
1069 os.environ['http_proxy'] = ''
1067
1070
1068 # unset env related to hooks
1071 # unset env related to hooks
1069 for k in os.environ.keys():
1072 for k in os.environ.keys():
1070 if k.startswith('HG_'):
1073 if k.startswith('HG_'):
1071 # can't remove on solaris
1074 # can't remove on solaris
1072 os.environ[k] = ''
1075 os.environ[k] = ''
1073 del os.environ[k]
1076 del os.environ[k]
1074
1077
1075 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1078 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1076 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1079 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1077 if options.tmpdir:
1080 if options.tmpdir:
1078 options.keep_tmpdir = True
1081 options.keep_tmpdir = True
1079 tmpdir = options.tmpdir
1082 tmpdir = options.tmpdir
1080 if os.path.exists(tmpdir):
1083 if os.path.exists(tmpdir):
1081 # Meaning of tmpdir has changed since 1.3: we used to create
1084 # Meaning of tmpdir has changed since 1.3: we used to create
1082 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1085 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1083 # tmpdir already exists.
1086 # tmpdir already exists.
1084 sys.exit("error: temp dir %r already exists" % tmpdir)
1087 sys.exit("error: temp dir %r already exists" % tmpdir)
1085
1088
1086 # Automatically removing tmpdir sounds convenient, but could
1089 # Automatically removing tmpdir sounds convenient, but could
1087 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1090 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1088 # or "--tmpdir=$HOME".
1091 # or "--tmpdir=$HOME".
1089 #vlog("# Removing temp dir", tmpdir)
1092 #vlog("# Removing temp dir", tmpdir)
1090 #shutil.rmtree(tmpdir)
1093 #shutil.rmtree(tmpdir)
1091 os.makedirs(tmpdir)
1094 os.makedirs(tmpdir)
1092 else:
1095 else:
1093 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1096 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1094 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1097 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1095 DAEMON_PIDS = None
1098 DAEMON_PIDS = None
1096 HGRCPATH = None
1099 HGRCPATH = None
1097
1100
1098 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1101 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1099 os.environ["HGMERGE"] = "internal:merge"
1102 os.environ["HGMERGE"] = "internal:merge"
1100 os.environ["HGUSER"] = "test"
1103 os.environ["HGUSER"] = "test"
1101 os.environ["HGENCODING"] = "ascii"
1104 os.environ["HGENCODING"] = "ascii"
1102 os.environ["HGENCODINGMODE"] = "strict"
1105 os.environ["HGENCODINGMODE"] = "strict"
1103 os.environ["HGPORT"] = str(options.port)
1106 os.environ["HGPORT"] = str(options.port)
1104 os.environ["HGPORT1"] = str(options.port + 1)
1107 os.environ["HGPORT1"] = str(options.port + 1)
1105 os.environ["HGPORT2"] = str(options.port + 2)
1108 os.environ["HGPORT2"] = str(options.port + 2)
1106
1109
1107 if options.with_hg:
1110 if options.with_hg:
1108 INST = None
1111 INST = None
1109 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1112 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1110
1113
1111 # This looks redundant with how Python initializes sys.path from
1114 # This looks redundant with how Python initializes sys.path from
1112 # the location of the script being executed. Needed because the
1115 # the location of the script being executed. Needed because the
1113 # "hg" specified by --with-hg is not the only Python script
1116 # "hg" specified by --with-hg is not the only Python script
1114 # executed in the test suite that needs to import 'mercurial'
1117 # executed in the test suite that needs to import 'mercurial'
1115 # ... which means it's not really redundant at all.
1118 # ... which means it's not really redundant at all.
1116 PYTHONDIR = BINDIR
1119 PYTHONDIR = BINDIR
1117 else:
1120 else:
1118 INST = os.path.join(HGTMP, "install")
1121 INST = os.path.join(HGTMP, "install")
1119 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1122 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1120 PYTHONDIR = os.path.join(INST, "lib", "python")
1123 PYTHONDIR = os.path.join(INST, "lib", "python")
1121
1124
1122 os.environ["BINDIR"] = BINDIR
1125 os.environ["BINDIR"] = BINDIR
1123 os.environ["PYTHON"] = PYTHON
1126 os.environ["PYTHON"] = PYTHON
1124
1127
1125 if not options.child:
1128 if not options.child:
1126 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1129 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1127 os.environ["PATH"] = os.pathsep.join(path)
1130 os.environ["PATH"] = os.pathsep.join(path)
1128
1131
1129 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1132 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1130 # can run .../tests/run-tests.py test-foo where test-foo
1133 # can run .../tests/run-tests.py test-foo where test-foo
1131 # adds an extension to HGRC
1134 # adds an extension to HGRC
1132 pypath = [PYTHONDIR, TESTDIR]
1135 pypath = [PYTHONDIR, TESTDIR]
1133 # We have to augment PYTHONPATH, rather than simply replacing
1136 # We have to augment PYTHONPATH, rather than simply replacing
1134 # it, in case external libraries are only available via current
1137 # it, in case external libraries are only available via current
1135 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1138 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1136 # are in /opt/subversion.)
1139 # are in /opt/subversion.)
1137 oldpypath = os.environ.get(IMPL_PATH)
1140 oldpypath = os.environ.get(IMPL_PATH)
1138 if oldpypath:
1141 if oldpypath:
1139 pypath.append(oldpypath)
1142 pypath.append(oldpypath)
1140 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1143 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1141
1144
1142 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1145 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1143
1146
1144 vlog("# Using TESTDIR", TESTDIR)
1147 vlog("# Using TESTDIR", TESTDIR)
1145 vlog("# Using HGTMP", HGTMP)
1148 vlog("# Using HGTMP", HGTMP)
1146 vlog("# Using PATH", os.environ["PATH"])
1149 vlog("# Using PATH", os.environ["PATH"])
1147 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1150 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1148
1151
1149 try:
1152 try:
1150 if len(tests) > 1 and options.jobs > 1:
1153 if len(tests) > 1 and options.jobs > 1:
1151 runchildren(options, tests)
1154 runchildren(options, tests)
1152 else:
1155 else:
1153 runtests(options, tests)
1156 runtests(options, tests)
1154 finally:
1157 finally:
1155 time.sleep(1)
1158 time.sleep(1)
1156 cleanup(options)
1159 cleanup(options)
1157
1160
1158 if __name__ == '__main__':
1161 if __name__ == '__main__':
1159 main()
1162 main()
General Comments 0
You need to be logged in to leave comments. Login now