##// END OF EJS Templates
run-tests: add --with-python3 to define a Python 3 interpreter...
Gregory Szorc -
r28582:cdbc2530 default
parent child Browse files
Show More
@@ -1,464 +1,468 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import errno
3 import errno
4 import os
4 import os
5 import re
5 import re
6 import socket
6 import socket
7 import stat
7 import stat
8 import subprocess
8 import subprocess
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11
11
12 tempprefix = 'hg-hghave-'
12 tempprefix = 'hg-hghave-'
13
13
14 checks = {
14 checks = {
15 "true": (lambda: True, "yak shaving"),
15 "true": (lambda: True, "yak shaving"),
16 "false": (lambda: False, "nail clipper"),
16 "false": (lambda: False, "nail clipper"),
17 }
17 }
18
18
19 def check(name, desc):
19 def check(name, desc):
20 def decorator(func):
20 def decorator(func):
21 checks[name] = (func, desc)
21 checks[name] = (func, desc)
22 return func
22 return func
23 return decorator
23 return decorator
24
24
25 def checkfeatures(features):
25 def checkfeatures(features):
26 result = {
26 result = {
27 'error': [],
27 'error': [],
28 'missing': [],
28 'missing': [],
29 'skipped': [],
29 'skipped': [],
30 }
30 }
31
31
32 for feature in features:
32 for feature in features:
33 negate = feature.startswith('no-')
33 negate = feature.startswith('no-')
34 if negate:
34 if negate:
35 feature = feature[3:]
35 feature = feature[3:]
36
36
37 if feature not in checks:
37 if feature not in checks:
38 result['missing'].append(feature)
38 result['missing'].append(feature)
39 continue
39 continue
40
40
41 check, desc = checks[feature]
41 check, desc = checks[feature]
42 try:
42 try:
43 available = check()
43 available = check()
44 except Exception:
44 except Exception:
45 result['error'].append('hghave check failed: %s' % feature)
45 result['error'].append('hghave check failed: %s' % feature)
46 continue
46 continue
47
47
48 if not negate and not available:
48 if not negate and not available:
49 result['skipped'].append('missing feature: %s' % desc)
49 result['skipped'].append('missing feature: %s' % desc)
50 elif negate and available:
50 elif negate and available:
51 result['skipped'].append('system supports %s' % desc)
51 result['skipped'].append('system supports %s' % desc)
52
52
53 return result
53 return result
54
54
55 def require(features):
55 def require(features):
56 """Require that features are available, exiting if not."""
56 """Require that features are available, exiting if not."""
57 result = checkfeatures(features)
57 result = checkfeatures(features)
58
58
59 for missing in result['missing']:
59 for missing in result['missing']:
60 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
60 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
61 for msg in result['skipped']:
61 for msg in result['skipped']:
62 sys.stderr.write('skipped: %s\n' % msg)
62 sys.stderr.write('skipped: %s\n' % msg)
63 for msg in result['error']:
63 for msg in result['error']:
64 sys.stderr.write('%s\n' % msg)
64 sys.stderr.write('%s\n' % msg)
65
65
66 if result['missing']:
66 if result['missing']:
67 sys.exit(2)
67 sys.exit(2)
68
68
69 if result['skipped'] or result['error']:
69 if result['skipped'] or result['error']:
70 sys.exit(1)
70 sys.exit(1)
71
71
72 def matchoutput(cmd, regexp, ignorestatus=False):
72 def matchoutput(cmd, regexp, ignorestatus=False):
73 """Return the match object if cmd executes successfully and its output
73 """Return the match object if cmd executes successfully and its output
74 is matched by the supplied regular expression.
74 is matched by the supplied regular expression.
75 """
75 """
76 r = re.compile(regexp)
76 r = re.compile(regexp)
77 try:
77 try:
78 p = subprocess.Popen(
78 p = subprocess.Popen(
79 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
79 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
80 except OSError as e:
80 except OSError as e:
81 if e.errno != errno.ENOENT:
81 if e.errno != errno.ENOENT:
82 raise
82 raise
83 ret = -1
83 ret = -1
84 ret = p.wait()
84 ret = p.wait()
85 s = p.stdout.read()
85 s = p.stdout.read()
86 return (ignorestatus or not ret) and r.search(s)
86 return (ignorestatus or not ret) and r.search(s)
87
87
88 @check("baz", "GNU Arch baz client")
88 @check("baz", "GNU Arch baz client")
89 def has_baz():
89 def has_baz():
90 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
90 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
91
91
92 @check("bzr", "Canonical's Bazaar client")
92 @check("bzr", "Canonical's Bazaar client")
93 def has_bzr():
93 def has_bzr():
94 try:
94 try:
95 import bzrlib
95 import bzrlib
96 return bzrlib.__doc__ is not None
96 return bzrlib.__doc__ is not None
97 except ImportError:
97 except ImportError:
98 return False
98 return False
99
99
100 @check("bzr114", "Canonical's Bazaar client >= 1.14")
100 @check("bzr114", "Canonical's Bazaar client >= 1.14")
101 def has_bzr114():
101 def has_bzr114():
102 try:
102 try:
103 import bzrlib
103 import bzrlib
104 return (bzrlib.__doc__ is not None
104 return (bzrlib.__doc__ is not None
105 and bzrlib.version_info[:2] >= (1, 14))
105 and bzrlib.version_info[:2] >= (1, 14))
106 except ImportError:
106 except ImportError:
107 return False
107 return False
108
108
109 @check("cvs", "cvs client/server")
109 @check("cvs", "cvs client/server")
110 def has_cvs():
110 def has_cvs():
111 re = r'Concurrent Versions System.*?server'
111 re = r'Concurrent Versions System.*?server'
112 return matchoutput('cvs --version 2>&1', re) and not has_msys()
112 return matchoutput('cvs --version 2>&1', re) and not has_msys()
113
113
114 @check("cvs112", "cvs client/server >= 1.12")
114 @check("cvs112", "cvs client/server >= 1.12")
115 def has_cvs112():
115 def has_cvs112():
116 re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
116 re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
117 return matchoutput('cvs --version 2>&1', re) and not has_msys()
117 return matchoutput('cvs --version 2>&1', re) and not has_msys()
118
118
119 @check("darcs", "darcs client")
119 @check("darcs", "darcs client")
120 def has_darcs():
120 def has_darcs():
121 return matchoutput('darcs --version', r'2\.[2-9]', True)
121 return matchoutput('darcs --version', r'2\.[2-9]', True)
122
122
123 @check("mtn", "monotone client (>= 1.0)")
123 @check("mtn", "monotone client (>= 1.0)")
124 def has_mtn():
124 def has_mtn():
125 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
125 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
126 'mtn --version', r'monotone 0\.', True)
126 'mtn --version', r'monotone 0\.', True)
127
127
128 @check("eol-in-paths", "end-of-lines in paths")
128 @check("eol-in-paths", "end-of-lines in paths")
129 def has_eol_in_paths():
129 def has_eol_in_paths():
130 try:
130 try:
131 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
131 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
132 os.close(fd)
132 os.close(fd)
133 os.remove(path)
133 os.remove(path)
134 return True
134 return True
135 except (IOError, OSError):
135 except (IOError, OSError):
136 return False
136 return False
137
137
138 @check("execbit", "executable bit")
138 @check("execbit", "executable bit")
139 def has_executablebit():
139 def has_executablebit():
140 try:
140 try:
141 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
141 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
142 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
142 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
143 try:
143 try:
144 os.close(fh)
144 os.close(fh)
145 m = os.stat(fn).st_mode & 0o777
145 m = os.stat(fn).st_mode & 0o777
146 new_file_has_exec = m & EXECFLAGS
146 new_file_has_exec = m & EXECFLAGS
147 os.chmod(fn, m ^ EXECFLAGS)
147 os.chmod(fn, m ^ EXECFLAGS)
148 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
148 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
149 finally:
149 finally:
150 os.unlink(fn)
150 os.unlink(fn)
151 except (IOError, OSError):
151 except (IOError, OSError):
152 # we don't care, the user probably won't be able to commit anyway
152 # we don't care, the user probably won't be able to commit anyway
153 return False
153 return False
154 return not (new_file_has_exec or exec_flags_cannot_flip)
154 return not (new_file_has_exec or exec_flags_cannot_flip)
155
155
156 @check("icasefs", "case insensitive file system")
156 @check("icasefs", "case insensitive file system")
157 def has_icasefs():
157 def has_icasefs():
158 # Stolen from mercurial.util
158 # Stolen from mercurial.util
159 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
159 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
160 os.close(fd)
160 os.close(fd)
161 try:
161 try:
162 s1 = os.stat(path)
162 s1 = os.stat(path)
163 d, b = os.path.split(path)
163 d, b = os.path.split(path)
164 p2 = os.path.join(d, b.upper())
164 p2 = os.path.join(d, b.upper())
165 if path == p2:
165 if path == p2:
166 p2 = os.path.join(d, b.lower())
166 p2 = os.path.join(d, b.lower())
167 try:
167 try:
168 s2 = os.stat(p2)
168 s2 = os.stat(p2)
169 return s2 == s1
169 return s2 == s1
170 except OSError:
170 except OSError:
171 return False
171 return False
172 finally:
172 finally:
173 os.remove(path)
173 os.remove(path)
174
174
175 @check("fifo", "named pipes")
175 @check("fifo", "named pipes")
176 def has_fifo():
176 def has_fifo():
177 if getattr(os, "mkfifo", None) is None:
177 if getattr(os, "mkfifo", None) is None:
178 return False
178 return False
179 name = tempfile.mktemp(dir='.', prefix=tempprefix)
179 name = tempfile.mktemp(dir='.', prefix=tempprefix)
180 try:
180 try:
181 os.mkfifo(name)
181 os.mkfifo(name)
182 os.unlink(name)
182 os.unlink(name)
183 return True
183 return True
184 except OSError:
184 except OSError:
185 return False
185 return False
186
186
187 @check("killdaemons", 'killdaemons.py support')
187 @check("killdaemons", 'killdaemons.py support')
188 def has_killdaemons():
188 def has_killdaemons():
189 return True
189 return True
190
190
191 @check("cacheable", "cacheable filesystem")
191 @check("cacheable", "cacheable filesystem")
192 def has_cacheable_fs():
192 def has_cacheable_fs():
193 from mercurial import util
193 from mercurial import util
194
194
195 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
195 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
196 os.close(fd)
196 os.close(fd)
197 try:
197 try:
198 return util.cachestat(path).cacheable()
198 return util.cachestat(path).cacheable()
199 finally:
199 finally:
200 os.remove(path)
200 os.remove(path)
201
201
202 @check("lsprof", "python lsprof module")
202 @check("lsprof", "python lsprof module")
203 def has_lsprof():
203 def has_lsprof():
204 try:
204 try:
205 import _lsprof
205 import _lsprof
206 _lsprof.Profiler # silence unused import warning
206 _lsprof.Profiler # silence unused import warning
207 return True
207 return True
208 except ImportError:
208 except ImportError:
209 return False
209 return False
210
210
211 @check("gettext", "GNU Gettext (msgfmt)")
211 @check("gettext", "GNU Gettext (msgfmt)")
212 def has_gettext():
212 def has_gettext():
213 return matchoutput('msgfmt --version', 'GNU gettext-tools')
213 return matchoutput('msgfmt --version', 'GNU gettext-tools')
214
214
215 @check("git", "git command line client")
215 @check("git", "git command line client")
216 def has_git():
216 def has_git():
217 return matchoutput('git --version 2>&1', r'^git version')
217 return matchoutput('git --version 2>&1', r'^git version')
218
218
219 @check("docutils", "Docutils text processing library")
219 @check("docutils", "Docutils text processing library")
220 def has_docutils():
220 def has_docutils():
221 try:
221 try:
222 from docutils.core import publish_cmdline
222 from docutils.core import publish_cmdline
223 publish_cmdline # silence unused import
223 publish_cmdline # silence unused import
224 return True
224 return True
225 except ImportError:
225 except ImportError:
226 return False
226 return False
227
227
228 def getsvnversion():
228 def getsvnversion():
229 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
229 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
230 if not m:
230 if not m:
231 return (0, 0)
231 return (0, 0)
232 return (int(m.group(1)), int(m.group(2)))
232 return (int(m.group(1)), int(m.group(2)))
233
233
234 @check("svn15", "subversion client and admin tools >= 1.5")
234 @check("svn15", "subversion client and admin tools >= 1.5")
235 def has_svn15():
235 def has_svn15():
236 return getsvnversion() >= (1, 5)
236 return getsvnversion() >= (1, 5)
237
237
238 @check("svn13", "subversion client and admin tools >= 1.3")
238 @check("svn13", "subversion client and admin tools >= 1.3")
239 def has_svn13():
239 def has_svn13():
240 return getsvnversion() >= (1, 3)
240 return getsvnversion() >= (1, 3)
241
241
242 @check("svn", "subversion client and admin tools")
242 @check("svn", "subversion client and admin tools")
243 def has_svn():
243 def has_svn():
244 return matchoutput('svn --version 2>&1', r'^svn, version') and \
244 return matchoutput('svn --version 2>&1', r'^svn, version') and \
245 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
245 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
246
246
247 @check("svn-bindings", "subversion python bindings")
247 @check("svn-bindings", "subversion python bindings")
248 def has_svn_bindings():
248 def has_svn_bindings():
249 try:
249 try:
250 import svn.core
250 import svn.core
251 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
251 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
252 if version < (1, 4):
252 if version < (1, 4):
253 return False
253 return False
254 return True
254 return True
255 except ImportError:
255 except ImportError:
256 return False
256 return False
257
257
258 @check("p4", "Perforce server and client")
258 @check("p4", "Perforce server and client")
259 def has_p4():
259 def has_p4():
260 return (matchoutput('p4 -V', r'Rev\. P4/') and
260 return (matchoutput('p4 -V', r'Rev\. P4/') and
261 matchoutput('p4d -V', r'Rev\. P4D/'))
261 matchoutput('p4d -V', r'Rev\. P4D/'))
262
262
263 @check("symlink", "symbolic links")
263 @check("symlink", "symbolic links")
264 def has_symlink():
264 def has_symlink():
265 if getattr(os, "symlink", None) is None:
265 if getattr(os, "symlink", None) is None:
266 return False
266 return False
267 name = tempfile.mktemp(dir='.', prefix=tempprefix)
267 name = tempfile.mktemp(dir='.', prefix=tempprefix)
268 try:
268 try:
269 os.symlink(".", name)
269 os.symlink(".", name)
270 os.unlink(name)
270 os.unlink(name)
271 return True
271 return True
272 except (OSError, AttributeError):
272 except (OSError, AttributeError):
273 return False
273 return False
274
274
275 @check("hardlink", "hardlinks")
275 @check("hardlink", "hardlinks")
276 def has_hardlink():
276 def has_hardlink():
277 from mercurial import util
277 from mercurial import util
278 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
278 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
279 os.close(fh)
279 os.close(fh)
280 name = tempfile.mktemp(dir='.', prefix=tempprefix)
280 name = tempfile.mktemp(dir='.', prefix=tempprefix)
281 try:
281 try:
282 util.oslink(fn, name)
282 util.oslink(fn, name)
283 os.unlink(name)
283 os.unlink(name)
284 return True
284 return True
285 except OSError:
285 except OSError:
286 return False
286 return False
287 finally:
287 finally:
288 os.unlink(fn)
288 os.unlink(fn)
289
289
290 @check("tla", "GNU Arch tla client")
290 @check("tla", "GNU Arch tla client")
291 def has_tla():
291 def has_tla():
292 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
292 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
293
293
294 @check("gpg", "gpg client")
294 @check("gpg", "gpg client")
295 def has_gpg():
295 def has_gpg():
296 return matchoutput('gpg --version 2>&1', r'GnuPG')
296 return matchoutput('gpg --version 2>&1', r'GnuPG')
297
297
298 @check("unix-permissions", "unix-style permissions")
298 @check("unix-permissions", "unix-style permissions")
299 def has_unix_permissions():
299 def has_unix_permissions():
300 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
300 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
301 try:
301 try:
302 fname = os.path.join(d, 'foo')
302 fname = os.path.join(d, 'foo')
303 for umask in (0o77, 0o07, 0o22):
303 for umask in (0o77, 0o07, 0o22):
304 os.umask(umask)
304 os.umask(umask)
305 f = open(fname, 'w')
305 f = open(fname, 'w')
306 f.close()
306 f.close()
307 mode = os.stat(fname).st_mode
307 mode = os.stat(fname).st_mode
308 os.unlink(fname)
308 os.unlink(fname)
309 if mode & 0o777 != ~umask & 0o666:
309 if mode & 0o777 != ~umask & 0o666:
310 return False
310 return False
311 return True
311 return True
312 finally:
312 finally:
313 os.rmdir(d)
313 os.rmdir(d)
314
314
315 @check("unix-socket", "AF_UNIX socket family")
315 @check("unix-socket", "AF_UNIX socket family")
316 def has_unix_socket():
316 def has_unix_socket():
317 return getattr(socket, 'AF_UNIX', None) is not None
317 return getattr(socket, 'AF_UNIX', None) is not None
318
318
319 @check("root", "root permissions")
319 @check("root", "root permissions")
320 def has_root():
320 def has_root():
321 return getattr(os, 'geteuid', None) and os.geteuid() == 0
321 return getattr(os, 'geteuid', None) and os.geteuid() == 0
322
322
323 @check("pyflakes", "Pyflakes python linter")
323 @check("pyflakes", "Pyflakes python linter")
324 def has_pyflakes():
324 def has_pyflakes():
325 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
325 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
326 r"<stdin>:1: 're' imported but unused",
326 r"<stdin>:1: 're' imported but unused",
327 True)
327 True)
328
328
329 @check("pygments", "Pygments source highlighting library")
329 @check("pygments", "Pygments source highlighting library")
330 def has_pygments():
330 def has_pygments():
331 try:
331 try:
332 import pygments
332 import pygments
333 pygments.highlight # silence unused import warning
333 pygments.highlight # silence unused import warning
334 return True
334 return True
335 except ImportError:
335 except ImportError:
336 return False
336 return False
337
337
338 @check("outer-repo", "outer repo")
338 @check("outer-repo", "outer repo")
339 def has_outer_repo():
339 def has_outer_repo():
340 # failing for other reasons than 'no repo' imply that there is a repo
340 # failing for other reasons than 'no repo' imply that there is a repo
341 return not matchoutput('hg root 2>&1',
341 return not matchoutput('hg root 2>&1',
342 r'abort: no repository found', True)
342 r'abort: no repository found', True)
343
343
344 @check("ssl", ("(python >= 2.6 ssl module and python OpenSSL) "
344 @check("ssl", ("(python >= 2.6 ssl module and python OpenSSL) "
345 "OR python >= 2.7.9 ssl"))
345 "OR python >= 2.7.9 ssl"))
346 def has_ssl():
346 def has_ssl():
347 try:
347 try:
348 import ssl
348 import ssl
349 if getattr(ssl, 'create_default_context', False):
349 if getattr(ssl, 'create_default_context', False):
350 return True
350 return True
351 import OpenSSL
351 import OpenSSL
352 OpenSSL.SSL.Context
352 OpenSSL.SSL.Context
353 return True
353 return True
354 except ImportError:
354 except ImportError:
355 return False
355 return False
356
356
357 @check("sslcontext", "python >= 2.7.9 ssl")
357 @check("sslcontext", "python >= 2.7.9 ssl")
358 def has_sslcontext():
358 def has_sslcontext():
359 try:
359 try:
360 import ssl
360 import ssl
361 ssl.SSLContext
361 ssl.SSLContext
362 return True
362 return True
363 except (ImportError, AttributeError):
363 except (ImportError, AttributeError):
364 return False
364 return False
365
365
366 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
366 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
367 def has_defaultcacerts():
367 def has_defaultcacerts():
368 from mercurial import sslutil
368 from mercurial import sslutil
369 return sslutil._defaultcacerts() != '!'
369 return sslutil._defaultcacerts() != '!'
370
370
371 @check("windows", "Windows")
371 @check("windows", "Windows")
372 def has_windows():
372 def has_windows():
373 return os.name == 'nt'
373 return os.name == 'nt'
374
374
375 @check("system-sh", "system() uses sh")
375 @check("system-sh", "system() uses sh")
376 def has_system_sh():
376 def has_system_sh():
377 return os.name != 'nt'
377 return os.name != 'nt'
378
378
379 @check("serve", "platform and python can manage 'hg serve -d'")
379 @check("serve", "platform and python can manage 'hg serve -d'")
380 def has_serve():
380 def has_serve():
381 return os.name != 'nt' # gross approximation
381 return os.name != 'nt' # gross approximation
382
382
383 @check("test-repo", "running tests from repository")
383 @check("test-repo", "running tests from repository")
384 def has_test_repo():
384 def has_test_repo():
385 t = os.environ["TESTDIR"]
385 t = os.environ["TESTDIR"]
386 return os.path.isdir(os.path.join(t, "..", ".hg"))
386 return os.path.isdir(os.path.join(t, "..", ".hg"))
387
387
388 @check("tic", "terminfo compiler and curses module")
388 @check("tic", "terminfo compiler and curses module")
389 def has_tic():
389 def has_tic():
390 try:
390 try:
391 import curses
391 import curses
392 curses.COLOR_BLUE
392 curses.COLOR_BLUE
393 return matchoutput('test -x "`which tic`"', '')
393 return matchoutput('test -x "`which tic`"', '')
394 except ImportError:
394 except ImportError:
395 return False
395 return False
396
396
397 @check("msys", "Windows with MSYS")
397 @check("msys", "Windows with MSYS")
398 def has_msys():
398 def has_msys():
399 return os.getenv('MSYSTEM')
399 return os.getenv('MSYSTEM')
400
400
401 @check("aix", "AIX")
401 @check("aix", "AIX")
402 def has_aix():
402 def has_aix():
403 return sys.platform.startswith("aix")
403 return sys.platform.startswith("aix")
404
404
405 @check("osx", "OS X")
405 @check("osx", "OS X")
406 def has_osx():
406 def has_osx():
407 return sys.platform == 'darwin'
407 return sys.platform == 'darwin'
408
408
409 @check("docker", "docker support")
409 @check("docker", "docker support")
410 def has_docker():
410 def has_docker():
411 pat = r'A self-sufficient runtime for linux containers\.'
411 pat = r'A self-sufficient runtime for linux containers\.'
412 if matchoutput('docker --help', pat):
412 if matchoutput('docker --help', pat):
413 if 'linux' not in sys.platform:
413 if 'linux' not in sys.platform:
414 # TODO: in theory we should be able to test docker-based
414 # TODO: in theory we should be able to test docker-based
415 # package creation on non-linux using boot2docker, but in
415 # package creation on non-linux using boot2docker, but in
416 # practice that requires extra coordination to make sure
416 # practice that requires extra coordination to make sure
417 # $TESTTEMP is going to be visible at the same path to the
417 # $TESTTEMP is going to be visible at the same path to the
418 # boot2docker VM. If we figure out how to verify that, we
418 # boot2docker VM. If we figure out how to verify that, we
419 # can use the following instead of just saying False:
419 # can use the following instead of just saying False:
420 # return 'DOCKER_HOST' in os.environ
420 # return 'DOCKER_HOST' in os.environ
421 return False
421 return False
422
422
423 return True
423 return True
424 return False
424 return False
425
425
426 @check("debhelper", "debian packaging tools")
426 @check("debhelper", "debian packaging tools")
427 def has_debhelper():
427 def has_debhelper():
428 dpkg = matchoutput('dpkg --version',
428 dpkg = matchoutput('dpkg --version',
429 "Debian `dpkg' package management program")
429 "Debian `dpkg' package management program")
430 dh = matchoutput('dh --help',
430 dh = matchoutput('dh --help',
431 'dh is a part of debhelper.', ignorestatus=True)
431 'dh is a part of debhelper.', ignorestatus=True)
432 dh_py2 = matchoutput('dh_python2 --help',
432 dh_py2 = matchoutput('dh_python2 --help',
433 'other supported Python versions')
433 'other supported Python versions')
434 return dpkg and dh and dh_py2
434 return dpkg and dh and dh_py2
435
435
436 @check("absimport", "absolute_import in __future__")
436 @check("absimport", "absolute_import in __future__")
437 def has_absimport():
437 def has_absimport():
438 import __future__
438 import __future__
439 from mercurial import util
439 from mercurial import util
440 return util.safehasattr(__future__, "absolute_import")
440 return util.safehasattr(__future__, "absolute_import")
441
441
442 @check("py3k", "running with Python 3.x")
442 @check("py3k", "running with Python 3.x")
443 def has_py3k():
443 def has_py3k():
444 return 3 == sys.version_info[0]
444 return 3 == sys.version_info[0]
445
445
446 @check("py3exe", "a Python 3.x interpreter is available")
447 def has_python3exe():
448 return 'PYTHON3' in os.environ
449
446 @check("pure", "running with pure Python code")
450 @check("pure", "running with pure Python code")
447 def has_pure():
451 def has_pure():
448 return any([
452 return any([
449 os.environ.get("HGMODULEPOLICY") == "py",
453 os.environ.get("HGMODULEPOLICY") == "py",
450 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
454 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
451 ])
455 ])
452
456
453 @check("slow", "allow slow tests")
457 @check("slow", "allow slow tests")
454 def has_slow():
458 def has_slow():
455 return os.environ.get('HGTEST_SLOW') == 'slow'
459 return os.environ.get('HGTEST_SLOW') == 'slow'
456
460
457 @check("hypothesis", "Hypothesis automated test generation")
461 @check("hypothesis", "Hypothesis automated test generation")
458 def has_hypothesis():
462 def has_hypothesis():
459 try:
463 try:
460 import hypothesis
464 import hypothesis
461 hypothesis.given
465 hypothesis.given
462 return True
466 return True
463 except ImportError:
467 except ImportError:
464 return False
468 return False
@@ -1,2463 +1,2492 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 # 10) parallel, pure, tests that call run-tests:
38 # 10) parallel, pure, tests that call run-tests:
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 #
40 #
41 # (You could use any subset of the tests: test-s* happens to match
41 # (You could use any subset of the tests: test-s* happens to match
42 # enough that it's worth doing parallel runs, few enough that it
42 # enough that it's worth doing parallel runs, few enough that it
43 # completes fairly quickly, includes both shell and Python scripts, and
43 # completes fairly quickly, includes both shell and Python scripts, and
44 # includes some scripts that run daemon processes.)
44 # includes some scripts that run daemon processes.)
45
45
46 from __future__ import print_function
46 from __future__ import print_function
47
47
48 from distutils import version
48 from distutils import version
49 import difflib
49 import difflib
50 import errno
50 import errno
51 import json
51 import json
52 import optparse
52 import optparse
53 import os
53 import os
54 import shutil
54 import shutil
55 import subprocess
55 import subprocess
56 import signal
56 import signal
57 import socket
57 import socket
58 import sys
58 import sys
59 import tempfile
59 import tempfile
60 import time
60 import time
61 import random
61 import random
62 import re
62 import re
63 import threading
63 import threading
64 import killdaemons as killmod
64 import killdaemons as killmod
65 try:
65 try:
66 import Queue as queue
66 import Queue as queue
67 except ImportError:
67 except ImportError:
68 import queue
68 import queue
69 from xml.dom import minidom
69 from xml.dom import minidom
70 import unittest
70 import unittest
71
71
72 osenvironb = getattr(os, 'environb', os.environ)
72 osenvironb = getattr(os, 'environb', os.environ)
73 processlock = threading.Lock()
73 processlock = threading.Lock()
74
74
75 if sys.version_info > (3, 5, 0):
75 if sys.version_info > (3, 5, 0):
76 PYTHON3 = True
76 PYTHON3 = True
77 xrange = range # we use xrange in one place, and we'd rather not use range
77 xrange = range # we use xrange in one place, and we'd rather not use range
78 def _bytespath(p):
78 def _bytespath(p):
79 return p.encode('utf-8')
79 return p.encode('utf-8')
80
80
81 def _strpath(p):
81 def _strpath(p):
82 return p.decode('utf-8')
82 return p.decode('utf-8')
83
83
84 elif sys.version_info >= (3, 0, 0):
84 elif sys.version_info >= (3, 0, 0):
85 print('%s is only supported on Python 3.5+ and 2.6-2.7, not %s' %
85 print('%s is only supported on Python 3.5+ and 2.6-2.7, not %s' %
86 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
86 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
87 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
87 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
88 else:
88 else:
89 PYTHON3 = False
89 PYTHON3 = False
90
90
91 # In python 2.x, path operations are generally done using
91 # In python 2.x, path operations are generally done using
92 # bytestrings by default, so we don't have to do any extra
92 # bytestrings by default, so we don't have to do any extra
93 # fiddling there. We define the wrapper functions anyway just to
93 # fiddling there. We define the wrapper functions anyway just to
94 # help keep code consistent between platforms.
94 # help keep code consistent between platforms.
95 def _bytespath(p):
95 def _bytespath(p):
96 return p
96 return p
97
97
98 _strpath = _bytespath
98 _strpath = _bytespath
99
99
100 # For Windows support
100 # For Windows support
101 wifexited = getattr(os, "WIFEXITED", lambda x: False)
101 wifexited = getattr(os, "WIFEXITED", lambda x: False)
102
102
103 def checkportisavailable(port):
103 def checkportisavailable(port):
104 """return true if a port seems free to bind on localhost"""
104 """return true if a port seems free to bind on localhost"""
105 try:
105 try:
106 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
106 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
107 s.bind(('localhost', port))
107 s.bind(('localhost', port))
108 s.close()
108 s.close()
109 return True
109 return True
110 except socket.error as exc:
110 except socket.error as exc:
111 if not exc.errno == errno.EADDRINUSE:
111 if not exc.errno == errno.EADDRINUSE:
112 raise
112 raise
113 return False
113 return False
114
114
115 closefds = os.name == 'posix'
115 closefds = os.name == 'posix'
116 def Popen4(cmd, wd, timeout, env=None):
116 def Popen4(cmd, wd, timeout, env=None):
117 processlock.acquire()
117 processlock.acquire()
118 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
118 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
119 close_fds=closefds,
119 close_fds=closefds,
120 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
120 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
121 stderr=subprocess.STDOUT)
121 stderr=subprocess.STDOUT)
122 processlock.release()
122 processlock.release()
123
123
124 p.fromchild = p.stdout
124 p.fromchild = p.stdout
125 p.tochild = p.stdin
125 p.tochild = p.stdin
126 p.childerr = p.stderr
126 p.childerr = p.stderr
127
127
128 p.timeout = False
128 p.timeout = False
129 if timeout:
129 if timeout:
130 def t():
130 def t():
131 start = time.time()
131 start = time.time()
132 while time.time() - start < timeout and p.returncode is None:
132 while time.time() - start < timeout and p.returncode is None:
133 time.sleep(.1)
133 time.sleep(.1)
134 p.timeout = True
134 p.timeout = True
135 if p.returncode is None:
135 if p.returncode is None:
136 terminate(p)
136 terminate(p)
137 threading.Thread(target=t).start()
137 threading.Thread(target=t).start()
138
138
139 return p
139 return p
140
140
141 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
141 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
142 IMPL_PATH = b'PYTHONPATH'
142 IMPL_PATH = b'PYTHONPATH'
143 if 'java' in sys.platform:
143 if 'java' in sys.platform:
144 IMPL_PATH = b'JYTHONPATH'
144 IMPL_PATH = b'JYTHONPATH'
145
145
146 defaults = {
146 defaults = {
147 'jobs': ('HGTEST_JOBS', 1),
147 'jobs': ('HGTEST_JOBS', 1),
148 'timeout': ('HGTEST_TIMEOUT', 180),
148 'timeout': ('HGTEST_TIMEOUT', 180),
149 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
149 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
150 'port': ('HGTEST_PORT', 20059),
150 'port': ('HGTEST_PORT', 20059),
151 'shell': ('HGTEST_SHELL', 'sh'),
151 'shell': ('HGTEST_SHELL', 'sh'),
152 }
152 }
153
153
154 def parselistfiles(files, listtype, warn=True):
154 def parselistfiles(files, listtype, warn=True):
155 entries = dict()
155 entries = dict()
156 for filename in files:
156 for filename in files:
157 try:
157 try:
158 path = os.path.expanduser(os.path.expandvars(filename))
158 path = os.path.expanduser(os.path.expandvars(filename))
159 f = open(path, "rb")
159 f = open(path, "rb")
160 except IOError as err:
160 except IOError as err:
161 if err.errno != errno.ENOENT:
161 if err.errno != errno.ENOENT:
162 raise
162 raise
163 if warn:
163 if warn:
164 print("warning: no such %s file: %s" % (listtype, filename))
164 print("warning: no such %s file: %s" % (listtype, filename))
165 continue
165 continue
166
166
167 for line in f.readlines():
167 for line in f.readlines():
168 line = line.split(b'#', 1)[0].strip()
168 line = line.split(b'#', 1)[0].strip()
169 if line:
169 if line:
170 entries[line] = filename
170 entries[line] = filename
171
171
172 f.close()
172 f.close()
173 return entries
173 return entries
174
174
175 def getparser():
175 def getparser():
176 """Obtain the OptionParser used by the CLI."""
176 """Obtain the OptionParser used by the CLI."""
177 parser = optparse.OptionParser("%prog [options] [tests]")
177 parser = optparse.OptionParser("%prog [options] [tests]")
178
178
179 # keep these sorted
179 # keep these sorted
180 parser.add_option("--blacklist", action="append",
180 parser.add_option("--blacklist", action="append",
181 help="skip tests listed in the specified blacklist file")
181 help="skip tests listed in the specified blacklist file")
182 parser.add_option("--whitelist", action="append",
182 parser.add_option("--whitelist", action="append",
183 help="always run tests listed in the specified whitelist file")
183 help="always run tests listed in the specified whitelist file")
184 parser.add_option("--changed", type="string",
184 parser.add_option("--changed", type="string",
185 help="run tests that are changed in parent rev or working directory")
185 help="run tests that are changed in parent rev or working directory")
186 parser.add_option("-C", "--annotate", action="store_true",
186 parser.add_option("-C", "--annotate", action="store_true",
187 help="output files annotated with coverage")
187 help="output files annotated with coverage")
188 parser.add_option("-c", "--cover", action="store_true",
188 parser.add_option("-c", "--cover", action="store_true",
189 help="print a test coverage report")
189 help="print a test coverage report")
190 parser.add_option("-d", "--debug", action="store_true",
190 parser.add_option("-d", "--debug", action="store_true",
191 help="debug mode: write output of test scripts to console"
191 help="debug mode: write output of test scripts to console"
192 " rather than capturing and diffing it (disables timeout)")
192 " rather than capturing and diffing it (disables timeout)")
193 parser.add_option("-f", "--first", action="store_true",
193 parser.add_option("-f", "--first", action="store_true",
194 help="exit on the first test failure")
194 help="exit on the first test failure")
195 parser.add_option("-H", "--htmlcov", action="store_true",
195 parser.add_option("-H", "--htmlcov", action="store_true",
196 help="create an HTML report of the coverage of the files")
196 help="create an HTML report of the coverage of the files")
197 parser.add_option("-i", "--interactive", action="store_true",
197 parser.add_option("-i", "--interactive", action="store_true",
198 help="prompt to accept changed output")
198 help="prompt to accept changed output")
199 parser.add_option("-j", "--jobs", type="int",
199 parser.add_option("-j", "--jobs", type="int",
200 help="number of jobs to run in parallel"
200 help="number of jobs to run in parallel"
201 " (default: $%s or %d)" % defaults['jobs'])
201 " (default: $%s or %d)" % defaults['jobs'])
202 parser.add_option("--keep-tmpdir", action="store_true",
202 parser.add_option("--keep-tmpdir", action="store_true",
203 help="keep temporary directory after running tests")
203 help="keep temporary directory after running tests")
204 parser.add_option("-k", "--keywords",
204 parser.add_option("-k", "--keywords",
205 help="run tests matching keywords")
205 help="run tests matching keywords")
206 parser.add_option("-l", "--local", action="store_true",
206 parser.add_option("-l", "--local", action="store_true",
207 help="shortcut for --with-hg=<testdir>/../hg")
207 help="shortcut for --with-hg=<testdir>/../hg")
208 parser.add_option("--loop", action="store_true",
208 parser.add_option("--loop", action="store_true",
209 help="loop tests repeatedly")
209 help="loop tests repeatedly")
210 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
210 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
211 help="run each test N times (default=1)", default=1)
211 help="run each test N times (default=1)", default=1)
212 parser.add_option("-n", "--nodiff", action="store_true",
212 parser.add_option("-n", "--nodiff", action="store_true",
213 help="skip showing test changes")
213 help="skip showing test changes")
214 parser.add_option("-p", "--port", type="int",
214 parser.add_option("-p", "--port", type="int",
215 help="port on which servers should listen"
215 help="port on which servers should listen"
216 " (default: $%s or %d)" % defaults['port'])
216 " (default: $%s or %d)" % defaults['port'])
217 parser.add_option("--compiler", type="string",
217 parser.add_option("--compiler", type="string",
218 help="compiler to build with")
218 help="compiler to build with")
219 parser.add_option("--pure", action="store_true",
219 parser.add_option("--pure", action="store_true",
220 help="use pure Python code instead of C extensions")
220 help="use pure Python code instead of C extensions")
221 parser.add_option("-R", "--restart", action="store_true",
221 parser.add_option("-R", "--restart", action="store_true",
222 help="restart at last error")
222 help="restart at last error")
223 parser.add_option("-r", "--retest", action="store_true",
223 parser.add_option("-r", "--retest", action="store_true",
224 help="retest failed tests")
224 help="retest failed tests")
225 parser.add_option("-S", "--noskips", action="store_true",
225 parser.add_option("-S", "--noskips", action="store_true",
226 help="don't report skip tests verbosely")
226 help="don't report skip tests verbosely")
227 parser.add_option("--shell", type="string",
227 parser.add_option("--shell", type="string",
228 help="shell to use (default: $%s or %s)" % defaults['shell'])
228 help="shell to use (default: $%s or %s)" % defaults['shell'])
229 parser.add_option("-t", "--timeout", type="int",
229 parser.add_option("-t", "--timeout", type="int",
230 help="kill errant tests after TIMEOUT seconds"
230 help="kill errant tests after TIMEOUT seconds"
231 " (default: $%s or %d)" % defaults['timeout'])
231 " (default: $%s or %d)" % defaults['timeout'])
232 parser.add_option("--slowtimeout", type="int",
232 parser.add_option("--slowtimeout", type="int",
233 help="kill errant slow tests after SLOWTIMEOUT seconds"
233 help="kill errant slow tests after SLOWTIMEOUT seconds"
234 " (default: $%s or %d)" % defaults['slowtimeout'])
234 " (default: $%s or %d)" % defaults['slowtimeout'])
235 parser.add_option("--time", action="store_true",
235 parser.add_option("--time", action="store_true",
236 help="time how long each test takes")
236 help="time how long each test takes")
237 parser.add_option("--json", action="store_true",
237 parser.add_option("--json", action="store_true",
238 help="store test result data in 'report.json' file")
238 help="store test result data in 'report.json' file")
239 parser.add_option("--tmpdir", type="string",
239 parser.add_option("--tmpdir", type="string",
240 help="run tests in the given temporary directory"
240 help="run tests in the given temporary directory"
241 " (implies --keep-tmpdir)")
241 " (implies --keep-tmpdir)")
242 parser.add_option("-v", "--verbose", action="store_true",
242 parser.add_option("-v", "--verbose", action="store_true",
243 help="output verbose messages")
243 help="output verbose messages")
244 parser.add_option("--xunit", type="string",
244 parser.add_option("--xunit", type="string",
245 help="record xunit results at specified path")
245 help="record xunit results at specified path")
246 parser.add_option("--view", type="string",
246 parser.add_option("--view", type="string",
247 help="external diff viewer")
247 help="external diff viewer")
248 parser.add_option("--with-hg", type="string",
248 parser.add_option("--with-hg", type="string",
249 metavar="HG",
249 metavar="HG",
250 help="test using specified hg script rather than a "
250 help="test using specified hg script rather than a "
251 "temporary installation")
251 "temporary installation")
252 parser.add_option("--chg", action="store_true",
252 parser.add_option("--chg", action="store_true",
253 help="install and use chg wrapper in place of hg")
253 help="install and use chg wrapper in place of hg")
254 parser.add_option("--with-chg", metavar="CHG",
254 parser.add_option("--with-chg", metavar="CHG",
255 help="use specified chg wrapper in place of hg")
255 help="use specified chg wrapper in place of hg")
256 parser.add_option("-3", "--py3k-warnings", action="store_true",
256 parser.add_option("-3", "--py3k-warnings", action="store_true",
257 help="enable Py3k warnings on Python 2.6+")
257 help="enable Py3k warnings on Python 2.6+")
258 # This option should be deleted once test-check-py3-compat.t and other
259 # Python 3 tests run with Python 3.
260 parser.add_option("--with-python3", metavar="PYTHON3",
261 help="Python 3 interpreter (if running under Python 2)"
262 " (TEMPORARY)")
258 parser.add_option('--extra-config-opt', action="append",
263 parser.add_option('--extra-config-opt', action="append",
259 help='set the given config opt in the test hgrc')
264 help='set the given config opt in the test hgrc')
260 parser.add_option('--random', action="store_true",
265 parser.add_option('--random', action="store_true",
261 help='run tests in random order')
266 help='run tests in random order')
262 parser.add_option('--profile-runner', action='store_true',
267 parser.add_option('--profile-runner', action='store_true',
263 help='run statprof on run-tests')
268 help='run statprof on run-tests')
264 parser.add_option('--allow-slow-tests', action='store_true',
269 parser.add_option('--allow-slow-tests', action='store_true',
265 help='allow extremely slow tests')
270 help='allow extremely slow tests')
266 parser.add_option('--showchannels', action='store_true',
271 parser.add_option('--showchannels', action='store_true',
267 help='show scheduling channels')
272 help='show scheduling channels')
268
273
269 for option, (envvar, default) in defaults.items():
274 for option, (envvar, default) in defaults.items():
270 defaults[option] = type(default)(os.environ.get(envvar, default))
275 defaults[option] = type(default)(os.environ.get(envvar, default))
271 parser.set_defaults(**defaults)
276 parser.set_defaults(**defaults)
272
277
273 return parser
278 return parser
274
279
275 def parseargs(args, parser):
280 def parseargs(args, parser):
276 """Parse arguments with our OptionParser and validate results."""
281 """Parse arguments with our OptionParser and validate results."""
277 (options, args) = parser.parse_args(args)
282 (options, args) = parser.parse_args(args)
278
283
279 # jython is always pure
284 # jython is always pure
280 if 'java' in sys.platform or '__pypy__' in sys.modules:
285 if 'java' in sys.platform or '__pypy__' in sys.modules:
281 options.pure = True
286 options.pure = True
282
287
283 if options.with_hg:
288 if options.with_hg:
284 options.with_hg = os.path.realpath(
289 options.with_hg = os.path.realpath(
285 os.path.expanduser(_bytespath(options.with_hg)))
290 os.path.expanduser(_bytespath(options.with_hg)))
286 if not (os.path.isfile(options.with_hg) and
291 if not (os.path.isfile(options.with_hg) and
287 os.access(options.with_hg, os.X_OK)):
292 os.access(options.with_hg, os.X_OK)):
288 parser.error('--with-hg must specify an executable hg script')
293 parser.error('--with-hg must specify an executable hg script')
289 if not os.path.basename(options.with_hg) == b'hg':
294 if not os.path.basename(options.with_hg) == b'hg':
290 sys.stderr.write('warning: --with-hg should specify an hg script\n')
295 sys.stderr.write('warning: --with-hg should specify an hg script\n')
291 if options.local:
296 if options.local:
292 testdir = os.path.dirname(_bytespath(os.path.realpath(sys.argv[0])))
297 testdir = os.path.dirname(_bytespath(os.path.realpath(sys.argv[0])))
293 hgbin = os.path.join(os.path.dirname(testdir), b'hg')
298 hgbin = os.path.join(os.path.dirname(testdir), b'hg')
294 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
299 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
295 parser.error('--local specified, but %r not found or not executable'
300 parser.error('--local specified, but %r not found or not executable'
296 % hgbin)
301 % hgbin)
297 options.with_hg = hgbin
302 options.with_hg = hgbin
298
303
299 if (options.chg or options.with_chg) and os.name == 'nt':
304 if (options.chg or options.with_chg) and os.name == 'nt':
300 parser.error('chg does not work on %s' % os.name)
305 parser.error('chg does not work on %s' % os.name)
301 if options.with_chg:
306 if options.with_chg:
302 options.chg = False # no installation to temporary location
307 options.chg = False # no installation to temporary location
303 options.with_chg = os.path.realpath(
308 options.with_chg = os.path.realpath(
304 os.path.expanduser(_bytespath(options.with_chg)))
309 os.path.expanduser(_bytespath(options.with_chg)))
305 if not (os.path.isfile(options.with_chg) and
310 if not (os.path.isfile(options.with_chg) and
306 os.access(options.with_chg, os.X_OK)):
311 os.access(options.with_chg, os.X_OK)):
307 parser.error('--with-chg must specify a chg executable')
312 parser.error('--with-chg must specify a chg executable')
308 if options.chg and options.with_hg:
313 if options.chg and options.with_hg:
309 # chg shares installation location with hg
314 # chg shares installation location with hg
310 parser.error('--chg does not work when --with-hg is specified '
315 parser.error('--chg does not work when --with-hg is specified '
311 '(use --with-chg instead)')
316 '(use --with-chg instead)')
312
317
313 options.anycoverage = options.cover or options.annotate or options.htmlcov
318 options.anycoverage = options.cover or options.annotate or options.htmlcov
314 if options.anycoverage:
319 if options.anycoverage:
315 try:
320 try:
316 import coverage
321 import coverage
317 covver = version.StrictVersion(coverage.__version__).version
322 covver = version.StrictVersion(coverage.__version__).version
318 if covver < (3, 3):
323 if covver < (3, 3):
319 parser.error('coverage options require coverage 3.3 or later')
324 parser.error('coverage options require coverage 3.3 or later')
320 except ImportError:
325 except ImportError:
321 parser.error('coverage options now require the coverage package')
326 parser.error('coverage options now require the coverage package')
322
327
323 if options.anycoverage and options.local:
328 if options.anycoverage and options.local:
324 # this needs some path mangling somewhere, I guess
329 # this needs some path mangling somewhere, I guess
325 parser.error("sorry, coverage options do not work when --local "
330 parser.error("sorry, coverage options do not work when --local "
326 "is specified")
331 "is specified")
327
332
328 if options.anycoverage and options.with_hg:
333 if options.anycoverage and options.with_hg:
329 parser.error("sorry, coverage options do not work when --with-hg "
334 parser.error("sorry, coverage options do not work when --with-hg "
330 "is specified")
335 "is specified")
331
336
332 global verbose
337 global verbose
333 if options.verbose:
338 if options.verbose:
334 verbose = ''
339 verbose = ''
335
340
336 if options.tmpdir:
341 if options.tmpdir:
337 options.tmpdir = os.path.expanduser(options.tmpdir)
342 options.tmpdir = os.path.expanduser(options.tmpdir)
338
343
339 if options.jobs < 1:
344 if options.jobs < 1:
340 parser.error('--jobs must be positive')
345 parser.error('--jobs must be positive')
341 if options.interactive and options.debug:
346 if options.interactive and options.debug:
342 parser.error("-i/--interactive and -d/--debug are incompatible")
347 parser.error("-i/--interactive and -d/--debug are incompatible")
343 if options.debug:
348 if options.debug:
344 if options.timeout != defaults['timeout']:
349 if options.timeout != defaults['timeout']:
345 sys.stderr.write(
350 sys.stderr.write(
346 'warning: --timeout option ignored with --debug\n')
351 'warning: --timeout option ignored with --debug\n')
347 if options.slowtimeout != defaults['slowtimeout']:
352 if options.slowtimeout != defaults['slowtimeout']:
348 sys.stderr.write(
353 sys.stderr.write(
349 'warning: --slowtimeout option ignored with --debug\n')
354 'warning: --slowtimeout option ignored with --debug\n')
350 options.timeout = 0
355 options.timeout = 0
351 options.slowtimeout = 0
356 options.slowtimeout = 0
352 if options.py3k_warnings:
357 if options.py3k_warnings:
353 if PYTHON3:
358 if PYTHON3:
354 parser.error(
359 parser.error(
355 '--py3k-warnings can only be used on Python 2.6 and 2.7')
360 '--py3k-warnings can only be used on Python 2.6 and 2.7')
361 if options.with_python3:
362 if PYTHON3:
363 parser.error('--with-python3 cannot be used when executing with '
364 'Python 3')
365
366 # Verify Python3 executable is acceptable.
367 proc = subprocess.Popen([options.with_python3, b'--version'],
368 stdout=subprocess.PIPE,
369 stderr=subprocess.STDOUT)
370 out, _err = proc.communicate()
371 ret = proc.wait()
372 if ret != 0:
373 parser.error('could not determine version of python 3')
374 if not out.startswith('Python '):
375 parser.error('unexpected output from python3 --version: %s' %
376 out)
377 vers = version.LooseVersion(out[len('Python '):])
378 if vers < version.LooseVersion('3.5.0'):
379 parser.error('--with-python3 version must be 3.5.0 or greater; '
380 'got %s' % out)
381
356 if options.blacklist:
382 if options.blacklist:
357 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
383 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
358 if options.whitelist:
384 if options.whitelist:
359 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
385 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
360 else:
386 else:
361 options.whitelisted = {}
387 options.whitelisted = {}
362
388
363 if options.showchannels:
389 if options.showchannels:
364 options.nodiff = True
390 options.nodiff = True
365
391
366 return (options, args)
392 return (options, args)
367
393
368 def rename(src, dst):
394 def rename(src, dst):
369 """Like os.rename(), trade atomicity and opened files friendliness
395 """Like os.rename(), trade atomicity and opened files friendliness
370 for existing destination support.
396 for existing destination support.
371 """
397 """
372 shutil.copy(src, dst)
398 shutil.copy(src, dst)
373 os.remove(src)
399 os.remove(src)
374
400
375 _unified_diff = difflib.unified_diff
401 _unified_diff = difflib.unified_diff
376 if PYTHON3:
402 if PYTHON3:
377 import functools
403 import functools
378 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
404 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
379
405
380 def getdiff(expected, output, ref, err):
406 def getdiff(expected, output, ref, err):
381 servefail = False
407 servefail = False
382 lines = []
408 lines = []
383 for line in _unified_diff(expected, output, ref, err):
409 for line in _unified_diff(expected, output, ref, err):
384 if line.startswith(b'+++') or line.startswith(b'---'):
410 if line.startswith(b'+++') or line.startswith(b'---'):
385 line = line.replace(b'\\', b'/')
411 line = line.replace(b'\\', b'/')
386 if line.endswith(b' \n'):
412 if line.endswith(b' \n'):
387 line = line[:-2] + b'\n'
413 line = line[:-2] + b'\n'
388 lines.append(line)
414 lines.append(line)
389 if not servefail and line.startswith(
415 if not servefail and line.startswith(
390 b'+ abort: child process failed to start'):
416 b'+ abort: child process failed to start'):
391 servefail = True
417 servefail = True
392
418
393 return servefail, lines
419 return servefail, lines
394
420
395 verbose = False
421 verbose = False
396 def vlog(*msg):
422 def vlog(*msg):
397 """Log only when in verbose mode."""
423 """Log only when in verbose mode."""
398 if verbose is False:
424 if verbose is False:
399 return
425 return
400
426
401 return log(*msg)
427 return log(*msg)
402
428
403 # Bytes that break XML even in a CDATA block: control characters 0-31
429 # Bytes that break XML even in a CDATA block: control characters 0-31
404 # sans \t, \n and \r
430 # sans \t, \n and \r
405 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
431 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
406
432
407 def cdatasafe(data):
433 def cdatasafe(data):
408 """Make a string safe to include in a CDATA block.
434 """Make a string safe to include in a CDATA block.
409
435
410 Certain control characters are illegal in a CDATA block, and
436 Certain control characters are illegal in a CDATA block, and
411 there's no way to include a ]]> in a CDATA either. This function
437 there's no way to include a ]]> in a CDATA either. This function
412 replaces illegal bytes with ? and adds a space between the ]] so
438 replaces illegal bytes with ? and adds a space between the ]] so
413 that it won't break the CDATA block.
439 that it won't break the CDATA block.
414 """
440 """
415 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
441 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
416
442
417 def log(*msg):
443 def log(*msg):
418 """Log something to stdout.
444 """Log something to stdout.
419
445
420 Arguments are strings to print.
446 Arguments are strings to print.
421 """
447 """
422 with iolock:
448 with iolock:
423 if verbose:
449 if verbose:
424 print(verbose, end=' ')
450 print(verbose, end=' ')
425 for m in msg:
451 for m in msg:
426 print(m, end=' ')
452 print(m, end=' ')
427 print()
453 print()
428 sys.stdout.flush()
454 sys.stdout.flush()
429
455
430 def terminate(proc):
456 def terminate(proc):
431 """Terminate subprocess (with fallback for Python versions < 2.6)"""
457 """Terminate subprocess (with fallback for Python versions < 2.6)"""
432 vlog('# Terminating process %d' % proc.pid)
458 vlog('# Terminating process %d' % proc.pid)
433 try:
459 try:
434 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
460 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
435 except OSError:
461 except OSError:
436 pass
462 pass
437
463
438 def killdaemons(pidfile):
464 def killdaemons(pidfile):
439 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
465 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
440 logfn=vlog)
466 logfn=vlog)
441
467
442 class Test(unittest.TestCase):
468 class Test(unittest.TestCase):
443 """Encapsulates a single, runnable test.
469 """Encapsulates a single, runnable test.
444
470
445 While this class conforms to the unittest.TestCase API, it differs in that
471 While this class conforms to the unittest.TestCase API, it differs in that
446 instances need to be instantiated manually. (Typically, unittest.TestCase
472 instances need to be instantiated manually. (Typically, unittest.TestCase
447 classes are instantiated automatically by scanning modules.)
473 classes are instantiated automatically by scanning modules.)
448 """
474 """
449
475
450 # Status code reserved for skipped tests (used by hghave).
476 # Status code reserved for skipped tests (used by hghave).
451 SKIPPED_STATUS = 80
477 SKIPPED_STATUS = 80
452
478
453 def __init__(self, path, tmpdir, keeptmpdir=False,
479 def __init__(self, path, tmpdir, keeptmpdir=False,
454 debug=False,
480 debug=False,
455 timeout=defaults['timeout'],
481 timeout=defaults['timeout'],
456 startport=defaults['port'], extraconfigopts=None,
482 startport=defaults['port'], extraconfigopts=None,
457 py3kwarnings=False, shell=None, hgcommand=None,
483 py3kwarnings=False, shell=None, hgcommand=None,
458 slowtimeout=defaults['slowtimeout']):
484 slowtimeout=defaults['slowtimeout']):
459 """Create a test from parameters.
485 """Create a test from parameters.
460
486
461 path is the full path to the file defining the test.
487 path is the full path to the file defining the test.
462
488
463 tmpdir is the main temporary directory to use for this test.
489 tmpdir is the main temporary directory to use for this test.
464
490
465 keeptmpdir determines whether to keep the test's temporary directory
491 keeptmpdir determines whether to keep the test's temporary directory
466 after execution. It defaults to removal (False).
492 after execution. It defaults to removal (False).
467
493
468 debug mode will make the test execute verbosely, with unfiltered
494 debug mode will make the test execute verbosely, with unfiltered
469 output.
495 output.
470
496
471 timeout controls the maximum run time of the test. It is ignored when
497 timeout controls the maximum run time of the test. It is ignored when
472 debug is True. See slowtimeout for tests with #require slow.
498 debug is True. See slowtimeout for tests with #require slow.
473
499
474 slowtimeout overrides timeout if the test has #require slow.
500 slowtimeout overrides timeout if the test has #require slow.
475
501
476 startport controls the starting port number to use for this test. Each
502 startport controls the starting port number to use for this test. Each
477 test will reserve 3 port numbers for execution. It is the caller's
503 test will reserve 3 port numbers for execution. It is the caller's
478 responsibility to allocate a non-overlapping port range to Test
504 responsibility to allocate a non-overlapping port range to Test
479 instances.
505 instances.
480
506
481 extraconfigopts is an iterable of extra hgrc config options. Values
507 extraconfigopts is an iterable of extra hgrc config options. Values
482 must have the form "key=value" (something understood by hgrc). Values
508 must have the form "key=value" (something understood by hgrc). Values
483 of the form "foo.key=value" will result in "[foo] key=value".
509 of the form "foo.key=value" will result in "[foo] key=value".
484
510
485 py3kwarnings enables Py3k warnings.
511 py3kwarnings enables Py3k warnings.
486
512
487 shell is the shell to execute tests in.
513 shell is the shell to execute tests in.
488 """
514 """
489 self.path = path
515 self.path = path
490 self.bname = os.path.basename(path)
516 self.bname = os.path.basename(path)
491 self.name = _strpath(self.bname)
517 self.name = _strpath(self.bname)
492 self._testdir = os.path.dirname(path)
518 self._testdir = os.path.dirname(path)
493 self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname)
519 self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname)
494
520
495 self._threadtmp = tmpdir
521 self._threadtmp = tmpdir
496 self._keeptmpdir = keeptmpdir
522 self._keeptmpdir = keeptmpdir
497 self._debug = debug
523 self._debug = debug
498 self._timeout = timeout
524 self._timeout = timeout
499 self._slowtimeout = slowtimeout
525 self._slowtimeout = slowtimeout
500 self._startport = startport
526 self._startport = startport
501 self._extraconfigopts = extraconfigopts or []
527 self._extraconfigopts = extraconfigopts or []
502 self._py3kwarnings = py3kwarnings
528 self._py3kwarnings = py3kwarnings
503 self._shell = _bytespath(shell)
529 self._shell = _bytespath(shell)
504 self._hgcommand = hgcommand or b'hg'
530 self._hgcommand = hgcommand or b'hg'
505
531
506 self._aborted = False
532 self._aborted = False
507 self._daemonpids = []
533 self._daemonpids = []
508 self._finished = None
534 self._finished = None
509 self._ret = None
535 self._ret = None
510 self._out = None
536 self._out = None
511 self._skipped = None
537 self._skipped = None
512 self._testtmp = None
538 self._testtmp = None
513
539
514 # If we're not in --debug mode and reference output file exists,
540 # If we're not in --debug mode and reference output file exists,
515 # check test output against it.
541 # check test output against it.
516 if debug:
542 if debug:
517 self._refout = None # to match "out is None"
543 self._refout = None # to match "out is None"
518 elif os.path.exists(self.refpath):
544 elif os.path.exists(self.refpath):
519 f = open(self.refpath, 'rb')
545 f = open(self.refpath, 'rb')
520 self._refout = f.read().splitlines(True)
546 self._refout = f.read().splitlines(True)
521 f.close()
547 f.close()
522 else:
548 else:
523 self._refout = []
549 self._refout = []
524
550
525 # needed to get base class __repr__ running
551 # needed to get base class __repr__ running
526 @property
552 @property
527 def _testMethodName(self):
553 def _testMethodName(self):
528 return self.name
554 return self.name
529
555
530 def __str__(self):
556 def __str__(self):
531 return self.name
557 return self.name
532
558
533 def shortDescription(self):
559 def shortDescription(self):
534 return self.name
560 return self.name
535
561
536 def setUp(self):
562 def setUp(self):
537 """Tasks to perform before run()."""
563 """Tasks to perform before run()."""
538 self._finished = False
564 self._finished = False
539 self._ret = None
565 self._ret = None
540 self._out = None
566 self._out = None
541 self._skipped = None
567 self._skipped = None
542
568
543 try:
569 try:
544 os.mkdir(self._threadtmp)
570 os.mkdir(self._threadtmp)
545 except OSError as e:
571 except OSError as e:
546 if e.errno != errno.EEXIST:
572 if e.errno != errno.EEXIST:
547 raise
573 raise
548
574
549 self._testtmp = os.path.join(self._threadtmp,
575 self._testtmp = os.path.join(self._threadtmp,
550 os.path.basename(self.path))
576 os.path.basename(self.path))
551 os.mkdir(self._testtmp)
577 os.mkdir(self._testtmp)
552
578
553 # Remove any previous output files.
579 # Remove any previous output files.
554 if os.path.exists(self.errpath):
580 if os.path.exists(self.errpath):
555 try:
581 try:
556 os.remove(self.errpath)
582 os.remove(self.errpath)
557 except OSError as e:
583 except OSError as e:
558 # We might have raced another test to clean up a .err
584 # We might have raced another test to clean up a .err
559 # file, so ignore ENOENT when removing a previous .err
585 # file, so ignore ENOENT when removing a previous .err
560 # file.
586 # file.
561 if e.errno != errno.ENOENT:
587 if e.errno != errno.ENOENT:
562 raise
588 raise
563
589
564 def run(self, result):
590 def run(self, result):
565 """Run this test and report results against a TestResult instance."""
591 """Run this test and report results against a TestResult instance."""
566 # This function is extremely similar to unittest.TestCase.run(). Once
592 # This function is extremely similar to unittest.TestCase.run(). Once
567 # we require Python 2.7 (or at least its version of unittest), this
593 # we require Python 2.7 (or at least its version of unittest), this
568 # function can largely go away.
594 # function can largely go away.
569 self._result = result
595 self._result = result
570 result.startTest(self)
596 result.startTest(self)
571 try:
597 try:
572 try:
598 try:
573 self.setUp()
599 self.setUp()
574 except (KeyboardInterrupt, SystemExit):
600 except (KeyboardInterrupt, SystemExit):
575 self._aborted = True
601 self._aborted = True
576 raise
602 raise
577 except Exception:
603 except Exception:
578 result.addError(self, sys.exc_info())
604 result.addError(self, sys.exc_info())
579 return
605 return
580
606
581 success = False
607 success = False
582 try:
608 try:
583 self.runTest()
609 self.runTest()
584 except KeyboardInterrupt:
610 except KeyboardInterrupt:
585 self._aborted = True
611 self._aborted = True
586 raise
612 raise
587 except SkipTest as e:
613 except SkipTest as e:
588 result.addSkip(self, str(e))
614 result.addSkip(self, str(e))
589 # The base class will have already counted this as a
615 # The base class will have already counted this as a
590 # test we "ran", but we want to exclude skipped tests
616 # test we "ran", but we want to exclude skipped tests
591 # from those we count towards those run.
617 # from those we count towards those run.
592 result.testsRun -= 1
618 result.testsRun -= 1
593 except IgnoreTest as e:
619 except IgnoreTest as e:
594 result.addIgnore(self, str(e))
620 result.addIgnore(self, str(e))
595 # As with skips, ignores also should be excluded from
621 # As with skips, ignores also should be excluded from
596 # the number of tests executed.
622 # the number of tests executed.
597 result.testsRun -= 1
623 result.testsRun -= 1
598 except WarnTest as e:
624 except WarnTest as e:
599 result.addWarn(self, str(e))
625 result.addWarn(self, str(e))
600 except ReportedTest as e:
626 except ReportedTest as e:
601 pass
627 pass
602 except self.failureException as e:
628 except self.failureException as e:
603 # This differs from unittest in that we don't capture
629 # This differs from unittest in that we don't capture
604 # the stack trace. This is for historical reasons and
630 # the stack trace. This is for historical reasons and
605 # this decision could be revisited in the future,
631 # this decision could be revisited in the future,
606 # especially for PythonTest instances.
632 # especially for PythonTest instances.
607 if result.addFailure(self, str(e)):
633 if result.addFailure(self, str(e)):
608 success = True
634 success = True
609 except Exception:
635 except Exception:
610 result.addError(self, sys.exc_info())
636 result.addError(self, sys.exc_info())
611 else:
637 else:
612 success = True
638 success = True
613
639
614 try:
640 try:
615 self.tearDown()
641 self.tearDown()
616 except (KeyboardInterrupt, SystemExit):
642 except (KeyboardInterrupt, SystemExit):
617 self._aborted = True
643 self._aborted = True
618 raise
644 raise
619 except Exception:
645 except Exception:
620 result.addError(self, sys.exc_info())
646 result.addError(self, sys.exc_info())
621 success = False
647 success = False
622
648
623 if success:
649 if success:
624 result.addSuccess(self)
650 result.addSuccess(self)
625 finally:
651 finally:
626 result.stopTest(self, interrupted=self._aborted)
652 result.stopTest(self, interrupted=self._aborted)
627
653
628 def runTest(self):
654 def runTest(self):
629 """Run this test instance.
655 """Run this test instance.
630
656
631 This will return a tuple describing the result of the test.
657 This will return a tuple describing the result of the test.
632 """
658 """
633 env = self._getenv()
659 env = self._getenv()
634 self._daemonpids.append(env['DAEMON_PIDS'])
660 self._daemonpids.append(env['DAEMON_PIDS'])
635 self._createhgrc(env['HGRCPATH'])
661 self._createhgrc(env['HGRCPATH'])
636
662
637 vlog('# Test', self.name)
663 vlog('# Test', self.name)
638
664
639 ret, out = self._run(env)
665 ret, out = self._run(env)
640 self._finished = True
666 self._finished = True
641 self._ret = ret
667 self._ret = ret
642 self._out = out
668 self._out = out
643
669
644 def describe(ret):
670 def describe(ret):
645 if ret < 0:
671 if ret < 0:
646 return 'killed by signal: %d' % -ret
672 return 'killed by signal: %d' % -ret
647 return 'returned error code %d' % ret
673 return 'returned error code %d' % ret
648
674
649 self._skipped = False
675 self._skipped = False
650
676
651 if ret == self.SKIPPED_STATUS:
677 if ret == self.SKIPPED_STATUS:
652 if out is None: # Debug mode, nothing to parse.
678 if out is None: # Debug mode, nothing to parse.
653 missing = ['unknown']
679 missing = ['unknown']
654 failed = None
680 failed = None
655 else:
681 else:
656 missing, failed = TTest.parsehghaveoutput(out)
682 missing, failed = TTest.parsehghaveoutput(out)
657
683
658 if not missing:
684 if not missing:
659 missing = ['skipped']
685 missing = ['skipped']
660
686
661 if failed:
687 if failed:
662 self.fail('hg have failed checking for %s' % failed[-1])
688 self.fail('hg have failed checking for %s' % failed[-1])
663 else:
689 else:
664 self._skipped = True
690 self._skipped = True
665 raise SkipTest(missing[-1])
691 raise SkipTest(missing[-1])
666 elif ret == 'timeout':
692 elif ret == 'timeout':
667 self.fail('timed out')
693 self.fail('timed out')
668 elif ret is False:
694 elif ret is False:
669 raise WarnTest('no result code from test')
695 raise WarnTest('no result code from test')
670 elif out != self._refout:
696 elif out != self._refout:
671 # Diff generation may rely on written .err file.
697 # Diff generation may rely on written .err file.
672 if (ret != 0 or out != self._refout) and not self._skipped \
698 if (ret != 0 or out != self._refout) and not self._skipped \
673 and not self._debug:
699 and not self._debug:
674 f = open(self.errpath, 'wb')
700 f = open(self.errpath, 'wb')
675 for line in out:
701 for line in out:
676 f.write(line)
702 f.write(line)
677 f.close()
703 f.close()
678
704
679 # The result object handles diff calculation for us.
705 # The result object handles diff calculation for us.
680 if self._result.addOutputMismatch(self, ret, out, self._refout):
706 if self._result.addOutputMismatch(self, ret, out, self._refout):
681 # change was accepted, skip failing
707 # change was accepted, skip failing
682 return
708 return
683
709
684 if ret:
710 if ret:
685 msg = 'output changed and ' + describe(ret)
711 msg = 'output changed and ' + describe(ret)
686 else:
712 else:
687 msg = 'output changed'
713 msg = 'output changed'
688
714
689 self.fail(msg)
715 self.fail(msg)
690 elif ret:
716 elif ret:
691 self.fail(describe(ret))
717 self.fail(describe(ret))
692
718
693 def tearDown(self):
719 def tearDown(self):
694 """Tasks to perform after run()."""
720 """Tasks to perform after run()."""
695 for entry in self._daemonpids:
721 for entry in self._daemonpids:
696 killdaemons(entry)
722 killdaemons(entry)
697 self._daemonpids = []
723 self._daemonpids = []
698
724
699 if self._keeptmpdir:
725 if self._keeptmpdir:
700 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
726 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
701 (self._testtmp.decode('utf-8'),
727 (self._testtmp.decode('utf-8'),
702 self._threadtmp.decode('utf-8')))
728 self._threadtmp.decode('utf-8')))
703 else:
729 else:
704 shutil.rmtree(self._testtmp, True)
730 shutil.rmtree(self._testtmp, True)
705 shutil.rmtree(self._threadtmp, True)
731 shutil.rmtree(self._threadtmp, True)
706
732
707 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
733 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
708 and not self._debug and self._out:
734 and not self._debug and self._out:
709 f = open(self.errpath, 'wb')
735 f = open(self.errpath, 'wb')
710 for line in self._out:
736 for line in self._out:
711 f.write(line)
737 f.write(line)
712 f.close()
738 f.close()
713
739
714 vlog("# Ret was:", self._ret, '(%s)' % self.name)
740 vlog("# Ret was:", self._ret, '(%s)' % self.name)
715
741
716 def _run(self, env):
742 def _run(self, env):
717 # This should be implemented in child classes to run tests.
743 # This should be implemented in child classes to run tests.
718 raise SkipTest('unknown test type')
744 raise SkipTest('unknown test type')
719
745
720 def abort(self):
746 def abort(self):
721 """Terminate execution of this test."""
747 """Terminate execution of this test."""
722 self._aborted = True
748 self._aborted = True
723
749
724 def _portmap(self, i):
750 def _portmap(self, i):
725 offset = b'' if i == 0 else b'%d' % i
751 offset = b'' if i == 0 else b'%d' % i
726 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
752 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
727
753
728 def _getreplacements(self):
754 def _getreplacements(self):
729 """Obtain a mapping of text replacements to apply to test output.
755 """Obtain a mapping of text replacements to apply to test output.
730
756
731 Test output needs to be normalized so it can be compared to expected
757 Test output needs to be normalized so it can be compared to expected
732 output. This function defines how some of that normalization will
758 output. This function defines how some of that normalization will
733 occur.
759 occur.
734 """
760 """
735 r = [
761 r = [
736 # This list should be parallel to defineport in _getenv
762 # This list should be parallel to defineport in _getenv
737 self._portmap(0),
763 self._portmap(0),
738 self._portmap(1),
764 self._portmap(1),
739 self._portmap(2),
765 self._portmap(2),
740 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
766 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
741 br'\1 (glob)'),
767 br'\1 (glob)'),
742 ]
768 ]
743 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
769 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
744
770
745 return r
771 return r
746
772
747 def _escapepath(self, p):
773 def _escapepath(self, p):
748 if os.name == 'nt':
774 if os.name == 'nt':
749 return (
775 return (
750 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
776 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
751 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
777 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
752 for c in p))
778 for c in p))
753 )
779 )
754 else:
780 else:
755 return re.escape(p)
781 return re.escape(p)
756
782
757 def _getenv(self):
783 def _getenv(self):
758 """Obtain environment variables to use during test execution."""
784 """Obtain environment variables to use during test execution."""
759 def defineport(i):
785 def defineport(i):
760 offset = '' if i == 0 else '%s' % i
786 offset = '' if i == 0 else '%s' % i
761 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
787 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
762 env = os.environ.copy()
788 env = os.environ.copy()
763 env['TESTTMP'] = self._testtmp
789 env['TESTTMP'] = self._testtmp
764 env['HOME'] = self._testtmp
790 env['HOME'] = self._testtmp
765 # This number should match portneeded in _getport
791 # This number should match portneeded in _getport
766 for port in xrange(3):
792 for port in xrange(3):
767 # This list should be parallel to _portmap in _getreplacements
793 # This list should be parallel to _portmap in _getreplacements
768 defineport(port)
794 defineport(port)
769 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
795 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
770 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
796 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
771 env["HGEDITOR"] = ('"' + sys.executable + '"'
797 env["HGEDITOR"] = ('"' + sys.executable + '"'
772 + ' -c "import sys; sys.exit(0)"')
798 + ' -c "import sys; sys.exit(0)"')
773 env["HGMERGE"] = "internal:merge"
799 env["HGMERGE"] = "internal:merge"
774 env["HGUSER"] = "test"
800 env["HGUSER"] = "test"
775 env["HGENCODING"] = "ascii"
801 env["HGENCODING"] = "ascii"
776 env["HGENCODINGMODE"] = "strict"
802 env["HGENCODINGMODE"] = "strict"
777
803
778 # Reset some environment variables to well-known values so that
804 # Reset some environment variables to well-known values so that
779 # the tests produce repeatable output.
805 # the tests produce repeatable output.
780 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
806 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
781 env['TZ'] = 'GMT'
807 env['TZ'] = 'GMT'
782 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
808 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
783 env['COLUMNS'] = '80'
809 env['COLUMNS'] = '80'
784 env['TERM'] = 'xterm'
810 env['TERM'] = 'xterm'
785
811
786 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
812 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
787 'NO_PROXY').split():
813 'NO_PROXY').split():
788 if k in env:
814 if k in env:
789 del env[k]
815 del env[k]
790
816
791 # unset env related to hooks
817 # unset env related to hooks
792 for k in env.keys():
818 for k in env.keys():
793 if k.startswith('HG_'):
819 if k.startswith('HG_'):
794 del env[k]
820 del env[k]
795
821
796 return env
822 return env
797
823
798 def _createhgrc(self, path):
824 def _createhgrc(self, path):
799 """Create an hgrc file for this test."""
825 """Create an hgrc file for this test."""
800 hgrc = open(path, 'wb')
826 hgrc = open(path, 'wb')
801 hgrc.write(b'[ui]\n')
827 hgrc.write(b'[ui]\n')
802 hgrc.write(b'slash = True\n')
828 hgrc.write(b'slash = True\n')
803 hgrc.write(b'interactive = False\n')
829 hgrc.write(b'interactive = False\n')
804 hgrc.write(b'mergemarkers = detailed\n')
830 hgrc.write(b'mergemarkers = detailed\n')
805 hgrc.write(b'promptecho = True\n')
831 hgrc.write(b'promptecho = True\n')
806 hgrc.write(b'[defaults]\n')
832 hgrc.write(b'[defaults]\n')
807 hgrc.write(b'backout = -d "0 0"\n')
833 hgrc.write(b'backout = -d "0 0"\n')
808 hgrc.write(b'commit = -d "0 0"\n')
834 hgrc.write(b'commit = -d "0 0"\n')
809 hgrc.write(b'shelve = --date "0 0"\n')
835 hgrc.write(b'shelve = --date "0 0"\n')
810 hgrc.write(b'tag = -d "0 0"\n')
836 hgrc.write(b'tag = -d "0 0"\n')
811 hgrc.write(b'[devel]\n')
837 hgrc.write(b'[devel]\n')
812 hgrc.write(b'all-warnings = true\n')
838 hgrc.write(b'all-warnings = true\n')
813 hgrc.write(b'[largefiles]\n')
839 hgrc.write(b'[largefiles]\n')
814 hgrc.write(b'usercache = %s\n' %
840 hgrc.write(b'usercache = %s\n' %
815 (os.path.join(self._testtmp, b'.cache/largefiles')))
841 (os.path.join(self._testtmp, b'.cache/largefiles')))
816
842
817 for opt in self._extraconfigopts:
843 for opt in self._extraconfigopts:
818 section, key = opt.split('.', 1)
844 section, key = opt.split('.', 1)
819 assert '=' in key, ('extra config opt %s must '
845 assert '=' in key, ('extra config opt %s must '
820 'have an = for assignment' % opt)
846 'have an = for assignment' % opt)
821 hgrc.write(b'[%s]\n%s\n' % (section, key))
847 hgrc.write(b'[%s]\n%s\n' % (section, key))
822 hgrc.close()
848 hgrc.close()
823
849
824 def fail(self, msg):
850 def fail(self, msg):
825 # unittest differentiates between errored and failed.
851 # unittest differentiates between errored and failed.
826 # Failed is denoted by AssertionError (by default at least).
852 # Failed is denoted by AssertionError (by default at least).
827 raise AssertionError(msg)
853 raise AssertionError(msg)
828
854
829 def _runcommand(self, cmd, env, normalizenewlines=False):
855 def _runcommand(self, cmd, env, normalizenewlines=False):
830 """Run command in a sub-process, capturing the output (stdout and
856 """Run command in a sub-process, capturing the output (stdout and
831 stderr).
857 stderr).
832
858
833 Return a tuple (exitcode, output). output is None in debug mode.
859 Return a tuple (exitcode, output). output is None in debug mode.
834 """
860 """
835 if self._debug:
861 if self._debug:
836 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
862 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
837 env=env)
863 env=env)
838 ret = proc.wait()
864 ret = proc.wait()
839 return (ret, None)
865 return (ret, None)
840
866
841 proc = Popen4(cmd, self._testtmp, self._timeout, env)
867 proc = Popen4(cmd, self._testtmp, self._timeout, env)
842 def cleanup():
868 def cleanup():
843 terminate(proc)
869 terminate(proc)
844 ret = proc.wait()
870 ret = proc.wait()
845 if ret == 0:
871 if ret == 0:
846 ret = signal.SIGTERM << 8
872 ret = signal.SIGTERM << 8
847 killdaemons(env['DAEMON_PIDS'])
873 killdaemons(env['DAEMON_PIDS'])
848 return ret
874 return ret
849
875
850 output = ''
876 output = ''
851 proc.tochild.close()
877 proc.tochild.close()
852
878
853 try:
879 try:
854 output = proc.fromchild.read()
880 output = proc.fromchild.read()
855 except KeyboardInterrupt:
881 except KeyboardInterrupt:
856 vlog('# Handling keyboard interrupt')
882 vlog('# Handling keyboard interrupt')
857 cleanup()
883 cleanup()
858 raise
884 raise
859
885
860 ret = proc.wait()
886 ret = proc.wait()
861 if wifexited(ret):
887 if wifexited(ret):
862 ret = os.WEXITSTATUS(ret)
888 ret = os.WEXITSTATUS(ret)
863
889
864 if proc.timeout:
890 if proc.timeout:
865 ret = 'timeout'
891 ret = 'timeout'
866
892
867 if ret:
893 if ret:
868 killdaemons(env['DAEMON_PIDS'])
894 killdaemons(env['DAEMON_PIDS'])
869
895
870 for s, r in self._getreplacements():
896 for s, r in self._getreplacements():
871 output = re.sub(s, r, output)
897 output = re.sub(s, r, output)
872
898
873 if normalizenewlines:
899 if normalizenewlines:
874 output = output.replace('\r\n', '\n')
900 output = output.replace('\r\n', '\n')
875
901
876 return ret, output.splitlines(True)
902 return ret, output.splitlines(True)
877
903
878 class PythonTest(Test):
904 class PythonTest(Test):
879 """A Python-based test."""
905 """A Python-based test."""
880
906
881 @property
907 @property
882 def refpath(self):
908 def refpath(self):
883 return os.path.join(self._testdir, b'%s.out' % self.bname)
909 return os.path.join(self._testdir, b'%s.out' % self.bname)
884
910
885 def _run(self, env):
911 def _run(self, env):
886 py3kswitch = self._py3kwarnings and b' -3' or b''
912 py3kswitch = self._py3kwarnings and b' -3' or b''
887 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
913 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
888 vlog("# Running", cmd)
914 vlog("# Running", cmd)
889 normalizenewlines = os.name == 'nt'
915 normalizenewlines = os.name == 'nt'
890 result = self._runcommand(cmd, env,
916 result = self._runcommand(cmd, env,
891 normalizenewlines=normalizenewlines)
917 normalizenewlines=normalizenewlines)
892 if self._aborted:
918 if self._aborted:
893 raise KeyboardInterrupt()
919 raise KeyboardInterrupt()
894
920
895 return result
921 return result
896
922
897 # This script may want to drop globs from lines matching these patterns on
923 # This script may want to drop globs from lines matching these patterns on
898 # Windows, but check-code.py wants a glob on these lines unconditionally. Don't
924 # Windows, but check-code.py wants a glob on these lines unconditionally. Don't
899 # warn if that is the case for anything matching these lines.
925 # warn if that is the case for anything matching these lines.
900 checkcodeglobpats = [
926 checkcodeglobpats = [
901 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
927 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
902 re.compile(br'^moving \S+/.*[^)]$'),
928 re.compile(br'^moving \S+/.*[^)]$'),
903 re.compile(br'^pulling from \$TESTTMP/.*[^)]$')
929 re.compile(br'^pulling from \$TESTTMP/.*[^)]$')
904 ]
930 ]
905
931
906 bchr = chr
932 bchr = chr
907 if PYTHON3:
933 if PYTHON3:
908 bchr = lambda x: bytes([x])
934 bchr = lambda x: bytes([x])
909
935
910 class TTest(Test):
936 class TTest(Test):
911 """A "t test" is a test backed by a .t file."""
937 """A "t test" is a test backed by a .t file."""
912
938
913 SKIPPED_PREFIX = b'skipped: '
939 SKIPPED_PREFIX = b'skipped: '
914 FAILED_PREFIX = b'hghave check failed: '
940 FAILED_PREFIX = b'hghave check failed: '
915 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
941 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
916
942
917 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
943 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
918 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
944 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
919 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
945 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
920
946
921 @property
947 @property
922 def refpath(self):
948 def refpath(self):
923 return os.path.join(self._testdir, self.bname)
949 return os.path.join(self._testdir, self.bname)
924
950
925 def _run(self, env):
951 def _run(self, env):
926 f = open(self.path, 'rb')
952 f = open(self.path, 'rb')
927 lines = f.readlines()
953 lines = f.readlines()
928 f.close()
954 f.close()
929
955
930 salt, script, after, expected = self._parsetest(lines)
956 salt, script, after, expected = self._parsetest(lines)
931
957
932 # Write out the generated script.
958 # Write out the generated script.
933 fname = b'%s.sh' % self._testtmp
959 fname = b'%s.sh' % self._testtmp
934 f = open(fname, 'wb')
960 f = open(fname, 'wb')
935 for l in script:
961 for l in script:
936 f.write(l)
962 f.write(l)
937 f.close()
963 f.close()
938
964
939 cmd = b'%s "%s"' % (self._shell, fname)
965 cmd = b'%s "%s"' % (self._shell, fname)
940 vlog("# Running", cmd)
966 vlog("# Running", cmd)
941
967
942 exitcode, output = self._runcommand(cmd, env)
968 exitcode, output = self._runcommand(cmd, env)
943
969
944 if self._aborted:
970 if self._aborted:
945 raise KeyboardInterrupt()
971 raise KeyboardInterrupt()
946
972
947 # Do not merge output if skipped. Return hghave message instead.
973 # Do not merge output if skipped. Return hghave message instead.
948 # Similarly, with --debug, output is None.
974 # Similarly, with --debug, output is None.
949 if exitcode == self.SKIPPED_STATUS or output is None:
975 if exitcode == self.SKIPPED_STATUS or output is None:
950 return exitcode, output
976 return exitcode, output
951
977
952 return self._processoutput(exitcode, output, salt, after, expected)
978 return self._processoutput(exitcode, output, salt, after, expected)
953
979
954 def _hghave(self, reqs):
980 def _hghave(self, reqs):
955 # TODO do something smarter when all other uses of hghave are gone.
981 # TODO do something smarter when all other uses of hghave are gone.
956 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
982 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
957 tdir = runtestdir.replace(b'\\', b'/')
983 tdir = runtestdir.replace(b'\\', b'/')
958 proc = Popen4(b'%s -c "%s/hghave %s"' %
984 proc = Popen4(b'%s -c "%s/hghave %s"' %
959 (self._shell, tdir, b' '.join(reqs)),
985 (self._shell, tdir, b' '.join(reqs)),
960 self._testtmp, 0, self._getenv())
986 self._testtmp, 0, self._getenv())
961 stdout, stderr = proc.communicate()
987 stdout, stderr = proc.communicate()
962 ret = proc.wait()
988 ret = proc.wait()
963 if wifexited(ret):
989 if wifexited(ret):
964 ret = os.WEXITSTATUS(ret)
990 ret = os.WEXITSTATUS(ret)
965 if ret == 2:
991 if ret == 2:
966 print(stdout)
992 print(stdout)
967 sys.exit(1)
993 sys.exit(1)
968
994
969 if ret != 0:
995 if ret != 0:
970 return False, stdout
996 return False, stdout
971
997
972 if 'slow' in reqs:
998 if 'slow' in reqs:
973 self._timeout = self._slowtimeout
999 self._timeout = self._slowtimeout
974 return True, None
1000 return True, None
975
1001
976 def _parsetest(self, lines):
1002 def _parsetest(self, lines):
977 # We generate a shell script which outputs unique markers to line
1003 # We generate a shell script which outputs unique markers to line
978 # up script results with our source. These markers include input
1004 # up script results with our source. These markers include input
979 # line number and the last return code.
1005 # line number and the last return code.
980 salt = b"SALT%d" % time.time()
1006 salt = b"SALT%d" % time.time()
981 def addsalt(line, inpython):
1007 def addsalt(line, inpython):
982 if inpython:
1008 if inpython:
983 script.append(b'%s %d 0\n' % (salt, line))
1009 script.append(b'%s %d 0\n' % (salt, line))
984 else:
1010 else:
985 script.append(b'echo %s %d $?\n' % (salt, line))
1011 script.append(b'echo %s %d $?\n' % (salt, line))
986
1012
987 script = []
1013 script = []
988
1014
989 # After we run the shell script, we re-unify the script output
1015 # After we run the shell script, we re-unify the script output
990 # with non-active parts of the source, with synchronization by our
1016 # with non-active parts of the source, with synchronization by our
991 # SALT line number markers. The after table contains the non-active
1017 # SALT line number markers. The after table contains the non-active
992 # components, ordered by line number.
1018 # components, ordered by line number.
993 after = {}
1019 after = {}
994
1020
995 # Expected shell script output.
1021 # Expected shell script output.
996 expected = {}
1022 expected = {}
997
1023
998 pos = prepos = -1
1024 pos = prepos = -1
999
1025
1000 # True or False when in a true or false conditional section
1026 # True or False when in a true or false conditional section
1001 skipping = None
1027 skipping = None
1002
1028
1003 # We keep track of whether or not we're in a Python block so we
1029 # We keep track of whether or not we're in a Python block so we
1004 # can generate the surrounding doctest magic.
1030 # can generate the surrounding doctest magic.
1005 inpython = False
1031 inpython = False
1006
1032
1007 if self._debug:
1033 if self._debug:
1008 script.append(b'set -x\n')
1034 script.append(b'set -x\n')
1009 if self._hgcommand != b'hg':
1035 if self._hgcommand != b'hg':
1010 script.append(b'alias hg="%s"\n' % self._hgcommand)
1036 script.append(b'alias hg="%s"\n' % self._hgcommand)
1011 if os.getenv('MSYSTEM'):
1037 if os.getenv('MSYSTEM'):
1012 script.append(b'alias pwd="pwd -W"\n')
1038 script.append(b'alias pwd="pwd -W"\n')
1013
1039
1014 for n, l in enumerate(lines):
1040 for n, l in enumerate(lines):
1015 if not l.endswith(b'\n'):
1041 if not l.endswith(b'\n'):
1016 l += b'\n'
1042 l += b'\n'
1017 if l.startswith(b'#require'):
1043 if l.startswith(b'#require'):
1018 lsplit = l.split()
1044 lsplit = l.split()
1019 if len(lsplit) < 2 or lsplit[0] != b'#require':
1045 if len(lsplit) < 2 or lsplit[0] != b'#require':
1020 after.setdefault(pos, []).append(' !!! invalid #require\n')
1046 after.setdefault(pos, []).append(' !!! invalid #require\n')
1021 haveresult, message = self._hghave(lsplit[1:])
1047 haveresult, message = self._hghave(lsplit[1:])
1022 if not haveresult:
1048 if not haveresult:
1023 script = [b'echo "%s"\nexit 80\n' % message]
1049 script = [b'echo "%s"\nexit 80\n' % message]
1024 break
1050 break
1025 after.setdefault(pos, []).append(l)
1051 after.setdefault(pos, []).append(l)
1026 elif l.startswith(b'#if'):
1052 elif l.startswith(b'#if'):
1027 lsplit = l.split()
1053 lsplit = l.split()
1028 if len(lsplit) < 2 or lsplit[0] != b'#if':
1054 if len(lsplit) < 2 or lsplit[0] != b'#if':
1029 after.setdefault(pos, []).append(' !!! invalid #if\n')
1055 after.setdefault(pos, []).append(' !!! invalid #if\n')
1030 if skipping is not None:
1056 if skipping is not None:
1031 after.setdefault(pos, []).append(' !!! nested #if\n')
1057 after.setdefault(pos, []).append(' !!! nested #if\n')
1032 skipping = not self._hghave(lsplit[1:])[0]
1058 skipping = not self._hghave(lsplit[1:])[0]
1033 after.setdefault(pos, []).append(l)
1059 after.setdefault(pos, []).append(l)
1034 elif l.startswith(b'#else'):
1060 elif l.startswith(b'#else'):
1035 if skipping is None:
1061 if skipping is None:
1036 after.setdefault(pos, []).append(' !!! missing #if\n')
1062 after.setdefault(pos, []).append(' !!! missing #if\n')
1037 skipping = not skipping
1063 skipping = not skipping
1038 after.setdefault(pos, []).append(l)
1064 after.setdefault(pos, []).append(l)
1039 elif l.startswith(b'#endif'):
1065 elif l.startswith(b'#endif'):
1040 if skipping is None:
1066 if skipping is None:
1041 after.setdefault(pos, []).append(' !!! missing #if\n')
1067 after.setdefault(pos, []).append(' !!! missing #if\n')
1042 skipping = None
1068 skipping = None
1043 after.setdefault(pos, []).append(l)
1069 after.setdefault(pos, []).append(l)
1044 elif skipping:
1070 elif skipping:
1045 after.setdefault(pos, []).append(l)
1071 after.setdefault(pos, []).append(l)
1046 elif l.startswith(b' >>> '): # python inlines
1072 elif l.startswith(b' >>> '): # python inlines
1047 after.setdefault(pos, []).append(l)
1073 after.setdefault(pos, []).append(l)
1048 prepos = pos
1074 prepos = pos
1049 pos = n
1075 pos = n
1050 if not inpython:
1076 if not inpython:
1051 # We've just entered a Python block. Add the header.
1077 # We've just entered a Python block. Add the header.
1052 inpython = True
1078 inpython = True
1053 addsalt(prepos, False) # Make sure we report the exit code.
1079 addsalt(prepos, False) # Make sure we report the exit code.
1054 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1080 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1055 addsalt(n, True)
1081 addsalt(n, True)
1056 script.append(l[2:])
1082 script.append(l[2:])
1057 elif l.startswith(b' ... '): # python inlines
1083 elif l.startswith(b' ... '): # python inlines
1058 after.setdefault(prepos, []).append(l)
1084 after.setdefault(prepos, []).append(l)
1059 script.append(l[2:])
1085 script.append(l[2:])
1060 elif l.startswith(b' $ '): # commands
1086 elif l.startswith(b' $ '): # commands
1061 if inpython:
1087 if inpython:
1062 script.append(b'EOF\n')
1088 script.append(b'EOF\n')
1063 inpython = False
1089 inpython = False
1064 after.setdefault(pos, []).append(l)
1090 after.setdefault(pos, []).append(l)
1065 prepos = pos
1091 prepos = pos
1066 pos = n
1092 pos = n
1067 addsalt(n, False)
1093 addsalt(n, False)
1068 cmd = l[4:].split()
1094 cmd = l[4:].split()
1069 if len(cmd) == 2 and cmd[0] == b'cd':
1095 if len(cmd) == 2 and cmd[0] == b'cd':
1070 l = b' $ cd %s || exit 1\n' % cmd[1]
1096 l = b' $ cd %s || exit 1\n' % cmd[1]
1071 script.append(l[4:])
1097 script.append(l[4:])
1072 elif l.startswith(b' > '): # continuations
1098 elif l.startswith(b' > '): # continuations
1073 after.setdefault(prepos, []).append(l)
1099 after.setdefault(prepos, []).append(l)
1074 script.append(l[4:])
1100 script.append(l[4:])
1075 elif l.startswith(b' '): # results
1101 elif l.startswith(b' '): # results
1076 # Queue up a list of expected results.
1102 # Queue up a list of expected results.
1077 expected.setdefault(pos, []).append(l[2:])
1103 expected.setdefault(pos, []).append(l[2:])
1078 else:
1104 else:
1079 if inpython:
1105 if inpython:
1080 script.append(b'EOF\n')
1106 script.append(b'EOF\n')
1081 inpython = False
1107 inpython = False
1082 # Non-command/result. Queue up for merged output.
1108 # Non-command/result. Queue up for merged output.
1083 after.setdefault(pos, []).append(l)
1109 after.setdefault(pos, []).append(l)
1084
1110
1085 if inpython:
1111 if inpython:
1086 script.append(b'EOF\n')
1112 script.append(b'EOF\n')
1087 if skipping is not None:
1113 if skipping is not None:
1088 after.setdefault(pos, []).append(' !!! missing #endif\n')
1114 after.setdefault(pos, []).append(' !!! missing #endif\n')
1089 addsalt(n + 1, False)
1115 addsalt(n + 1, False)
1090
1116
1091 return salt, script, after, expected
1117 return salt, script, after, expected
1092
1118
1093 def _processoutput(self, exitcode, output, salt, after, expected):
1119 def _processoutput(self, exitcode, output, salt, after, expected):
1094 # Merge the script output back into a unified test.
1120 # Merge the script output back into a unified test.
1095 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1121 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1096 if exitcode != 0:
1122 if exitcode != 0:
1097 warnonly = 3
1123 warnonly = 3
1098
1124
1099 pos = -1
1125 pos = -1
1100 postout = []
1126 postout = []
1101 for l in output:
1127 for l in output:
1102 lout, lcmd = l, None
1128 lout, lcmd = l, None
1103 if salt in l:
1129 if salt in l:
1104 lout, lcmd = l.split(salt, 1)
1130 lout, lcmd = l.split(salt, 1)
1105
1131
1106 while lout:
1132 while lout:
1107 if not lout.endswith(b'\n'):
1133 if not lout.endswith(b'\n'):
1108 lout += b' (no-eol)\n'
1134 lout += b' (no-eol)\n'
1109
1135
1110 # Find the expected output at the current position.
1136 # Find the expected output at the current position.
1111 els = [None]
1137 els = [None]
1112 if expected.get(pos, None):
1138 if expected.get(pos, None):
1113 els = expected[pos]
1139 els = expected[pos]
1114
1140
1115 i = 0
1141 i = 0
1116 while i < len(els):
1142 while i < len(els):
1117 el = els[i]
1143 el = els[i]
1118
1144
1119 r = TTest.linematch(el, lout)
1145 r = TTest.linematch(el, lout)
1120 if isinstance(r, str):
1146 if isinstance(r, str):
1121 if r == '+glob':
1147 if r == '+glob':
1122 lout = el[:-1] + ' (glob)\n'
1148 lout = el[:-1] + ' (glob)\n'
1123 r = '' # Warn only this line.
1149 r = '' # Warn only this line.
1124 elif r == '-glob':
1150 elif r == '-glob':
1125 lout = ''.join(el.rsplit(' (glob)', 1))
1151 lout = ''.join(el.rsplit(' (glob)', 1))
1126 r = '' # Warn only this line.
1152 r = '' # Warn only this line.
1127 elif r == "retry":
1153 elif r == "retry":
1128 postout.append(b' ' + el)
1154 postout.append(b' ' + el)
1129 els.pop(i)
1155 els.pop(i)
1130 break
1156 break
1131 else:
1157 else:
1132 log('\ninfo, unknown linematch result: %r\n' % r)
1158 log('\ninfo, unknown linematch result: %r\n' % r)
1133 r = False
1159 r = False
1134 if r:
1160 if r:
1135 els.pop(i)
1161 els.pop(i)
1136 break
1162 break
1137 i += 1
1163 i += 1
1138
1164
1139 if r:
1165 if r:
1140 if r == "retry":
1166 if r == "retry":
1141 continue
1167 continue
1142 postout.append(b' ' + el)
1168 postout.append(b' ' + el)
1143 else:
1169 else:
1144 if self.NEEDESCAPE(lout):
1170 if self.NEEDESCAPE(lout):
1145 lout = TTest._stringescape(b'%s (esc)\n' %
1171 lout = TTest._stringescape(b'%s (esc)\n' %
1146 lout.rstrip(b'\n'))
1172 lout.rstrip(b'\n'))
1147 postout.append(b' ' + lout) # Let diff deal with it.
1173 postout.append(b' ' + lout) # Let diff deal with it.
1148 if r != '': # If line failed.
1174 if r != '': # If line failed.
1149 warnonly = 3 # for sure not
1175 warnonly = 3 # for sure not
1150 elif warnonly == 1: # Is "not yet" and line is warn only.
1176 elif warnonly == 1: # Is "not yet" and line is warn only.
1151 warnonly = 2 # Yes do warn.
1177 warnonly = 2 # Yes do warn.
1152 break
1178 break
1153 else:
1179 else:
1154 # clean up any optional leftovers
1180 # clean up any optional leftovers
1155 while expected.get(pos, None):
1181 while expected.get(pos, None):
1156 el = expected[pos].pop(0)
1182 el = expected[pos].pop(0)
1157 if el and not el.endswith(b" (?)\n"):
1183 if el and not el.endswith(b" (?)\n"):
1158 break
1184 break
1159 postout.append(b' ' + el)
1185 postout.append(b' ' + el)
1160
1186
1161 if lcmd:
1187 if lcmd:
1162 # Add on last return code.
1188 # Add on last return code.
1163 ret = int(lcmd.split()[1])
1189 ret = int(lcmd.split()[1])
1164 if ret != 0:
1190 if ret != 0:
1165 postout.append(b' [%d]\n' % ret)
1191 postout.append(b' [%d]\n' % ret)
1166 if pos in after:
1192 if pos in after:
1167 # Merge in non-active test bits.
1193 # Merge in non-active test bits.
1168 postout += after.pop(pos)
1194 postout += after.pop(pos)
1169 pos = int(lcmd.split()[0])
1195 pos = int(lcmd.split()[0])
1170
1196
1171 if pos in after:
1197 if pos in after:
1172 postout += after.pop(pos)
1198 postout += after.pop(pos)
1173
1199
1174 if warnonly == 2:
1200 if warnonly == 2:
1175 exitcode = False # Set exitcode to warned.
1201 exitcode = False # Set exitcode to warned.
1176
1202
1177 return exitcode, postout
1203 return exitcode, postout
1178
1204
1179 @staticmethod
1205 @staticmethod
1180 def rematch(el, l):
1206 def rematch(el, l):
1181 try:
1207 try:
1182 # use \Z to ensure that the regex matches to the end of the string
1208 # use \Z to ensure that the regex matches to the end of the string
1183 if os.name == 'nt':
1209 if os.name == 'nt':
1184 return re.match(el + br'\r?\n\Z', l)
1210 return re.match(el + br'\r?\n\Z', l)
1185 return re.match(el + br'\n\Z', l)
1211 return re.match(el + br'\n\Z', l)
1186 except re.error:
1212 except re.error:
1187 # el is an invalid regex
1213 # el is an invalid regex
1188 return False
1214 return False
1189
1215
1190 @staticmethod
1216 @staticmethod
1191 def globmatch(el, l):
1217 def globmatch(el, l):
1192 # The only supported special characters are * and ? plus / which also
1218 # The only supported special characters are * and ? plus / which also
1193 # matches \ on windows. Escaping of these characters is supported.
1219 # matches \ on windows. Escaping of these characters is supported.
1194 if el + b'\n' == l:
1220 if el + b'\n' == l:
1195 if os.altsep:
1221 if os.altsep:
1196 # matching on "/" is not needed for this line
1222 # matching on "/" is not needed for this line
1197 for pat in checkcodeglobpats:
1223 for pat in checkcodeglobpats:
1198 if pat.match(el):
1224 if pat.match(el):
1199 return True
1225 return True
1200 return b'-glob'
1226 return b'-glob'
1201 return True
1227 return True
1202 i, n = 0, len(el)
1228 i, n = 0, len(el)
1203 res = b''
1229 res = b''
1204 while i < n:
1230 while i < n:
1205 c = el[i:i + 1]
1231 c = el[i:i + 1]
1206 i += 1
1232 i += 1
1207 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1233 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1208 res += el[i - 1:i + 1]
1234 res += el[i - 1:i + 1]
1209 i += 1
1235 i += 1
1210 elif c == b'*':
1236 elif c == b'*':
1211 res += b'.*'
1237 res += b'.*'
1212 elif c == b'?':
1238 elif c == b'?':
1213 res += b'.'
1239 res += b'.'
1214 elif c == b'/' and os.altsep:
1240 elif c == b'/' and os.altsep:
1215 res += b'[/\\\\]'
1241 res += b'[/\\\\]'
1216 else:
1242 else:
1217 res += re.escape(c)
1243 res += re.escape(c)
1218 return TTest.rematch(res, l)
1244 return TTest.rematch(res, l)
1219
1245
1220 @staticmethod
1246 @staticmethod
1221 def linematch(el, l):
1247 def linematch(el, l):
1222 retry = False
1248 retry = False
1223 if el == l: # perfect match (fast)
1249 if el == l: # perfect match (fast)
1224 return True
1250 return True
1225 if el:
1251 if el:
1226 if el.endswith(b" (?)\n"):
1252 if el.endswith(b" (?)\n"):
1227 retry = "retry"
1253 retry = "retry"
1228 el = el[:-5] + b"\n"
1254 el = el[:-5] + b"\n"
1229 if el.endswith(b" (esc)\n"):
1255 if el.endswith(b" (esc)\n"):
1230 if PYTHON3:
1256 if PYTHON3:
1231 el = el[:-7].decode('unicode_escape') + '\n'
1257 el = el[:-7].decode('unicode_escape') + '\n'
1232 el = el.encode('utf-8')
1258 el = el.encode('utf-8')
1233 else:
1259 else:
1234 el = el[:-7].decode('string-escape') + '\n'
1260 el = el[:-7].decode('string-escape') + '\n'
1235 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1261 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1236 return True
1262 return True
1237 if el.endswith(b" (re)\n"):
1263 if el.endswith(b" (re)\n"):
1238 return TTest.rematch(el[:-6], l) or retry
1264 return TTest.rematch(el[:-6], l) or retry
1239 if el.endswith(b" (glob)\n"):
1265 if el.endswith(b" (glob)\n"):
1240 # ignore '(glob)' added to l by 'replacements'
1266 # ignore '(glob)' added to l by 'replacements'
1241 if l.endswith(b" (glob)\n"):
1267 if l.endswith(b" (glob)\n"):
1242 l = l[:-8] + b"\n"
1268 l = l[:-8] + b"\n"
1243 return TTest.globmatch(el[:-8], l)
1269 return TTest.globmatch(el[:-8], l)
1244 if os.altsep and l.replace(b'\\', b'/') == el:
1270 if os.altsep and l.replace(b'\\', b'/') == el:
1245 return b'+glob'
1271 return b'+glob'
1246 return retry
1272 return retry
1247
1273
1248 @staticmethod
1274 @staticmethod
1249 def parsehghaveoutput(lines):
1275 def parsehghaveoutput(lines):
1250 '''Parse hghave log lines.
1276 '''Parse hghave log lines.
1251
1277
1252 Return tuple of lists (missing, failed):
1278 Return tuple of lists (missing, failed):
1253 * the missing/unknown features
1279 * the missing/unknown features
1254 * the features for which existence check failed'''
1280 * the features for which existence check failed'''
1255 missing = []
1281 missing = []
1256 failed = []
1282 failed = []
1257 for line in lines:
1283 for line in lines:
1258 if line.startswith(TTest.SKIPPED_PREFIX):
1284 if line.startswith(TTest.SKIPPED_PREFIX):
1259 line = line.splitlines()[0]
1285 line = line.splitlines()[0]
1260 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1286 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1261 elif line.startswith(TTest.FAILED_PREFIX):
1287 elif line.startswith(TTest.FAILED_PREFIX):
1262 line = line.splitlines()[0]
1288 line = line.splitlines()[0]
1263 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1289 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1264
1290
1265 return missing, failed
1291 return missing, failed
1266
1292
1267 @staticmethod
1293 @staticmethod
1268 def _escapef(m):
1294 def _escapef(m):
1269 return TTest.ESCAPEMAP[m.group(0)]
1295 return TTest.ESCAPEMAP[m.group(0)]
1270
1296
1271 @staticmethod
1297 @staticmethod
1272 def _stringescape(s):
1298 def _stringescape(s):
1273 return TTest.ESCAPESUB(TTest._escapef, s)
1299 return TTest.ESCAPESUB(TTest._escapef, s)
1274
1300
1275 iolock = threading.RLock()
1301 iolock = threading.RLock()
1276
1302
1277 class SkipTest(Exception):
1303 class SkipTest(Exception):
1278 """Raised to indicate that a test is to be skipped."""
1304 """Raised to indicate that a test is to be skipped."""
1279
1305
1280 class IgnoreTest(Exception):
1306 class IgnoreTest(Exception):
1281 """Raised to indicate that a test is to be ignored."""
1307 """Raised to indicate that a test is to be ignored."""
1282
1308
1283 class WarnTest(Exception):
1309 class WarnTest(Exception):
1284 """Raised to indicate that a test warned."""
1310 """Raised to indicate that a test warned."""
1285
1311
1286 class ReportedTest(Exception):
1312 class ReportedTest(Exception):
1287 """Raised to indicate that a test already reported."""
1313 """Raised to indicate that a test already reported."""
1288
1314
1289 class TestResult(unittest._TextTestResult):
1315 class TestResult(unittest._TextTestResult):
1290 """Holds results when executing via unittest."""
1316 """Holds results when executing via unittest."""
1291 # Don't worry too much about accessing the non-public _TextTestResult.
1317 # Don't worry too much about accessing the non-public _TextTestResult.
1292 # It is relatively common in Python testing tools.
1318 # It is relatively common in Python testing tools.
1293 def __init__(self, options, *args, **kwargs):
1319 def __init__(self, options, *args, **kwargs):
1294 super(TestResult, self).__init__(*args, **kwargs)
1320 super(TestResult, self).__init__(*args, **kwargs)
1295
1321
1296 self._options = options
1322 self._options = options
1297
1323
1298 # unittest.TestResult didn't have skipped until 2.7. We need to
1324 # unittest.TestResult didn't have skipped until 2.7. We need to
1299 # polyfill it.
1325 # polyfill it.
1300 self.skipped = []
1326 self.skipped = []
1301
1327
1302 # We have a custom "ignored" result that isn't present in any Python
1328 # We have a custom "ignored" result that isn't present in any Python
1303 # unittest implementation. It is very similar to skipped. It may make
1329 # unittest implementation. It is very similar to skipped. It may make
1304 # sense to map it into skip some day.
1330 # sense to map it into skip some day.
1305 self.ignored = []
1331 self.ignored = []
1306
1332
1307 # We have a custom "warned" result that isn't present in any Python
1333 # We have a custom "warned" result that isn't present in any Python
1308 # unittest implementation. It is very similar to failed. It may make
1334 # unittest implementation. It is very similar to failed. It may make
1309 # sense to map it into fail some day.
1335 # sense to map it into fail some day.
1310 self.warned = []
1336 self.warned = []
1311
1337
1312 self.times = []
1338 self.times = []
1313 self._firststarttime = None
1339 self._firststarttime = None
1314 # Data stored for the benefit of generating xunit reports.
1340 # Data stored for the benefit of generating xunit reports.
1315 self.successes = []
1341 self.successes = []
1316 self.faildata = {}
1342 self.faildata = {}
1317
1343
1318 def addFailure(self, test, reason):
1344 def addFailure(self, test, reason):
1319 self.failures.append((test, reason))
1345 self.failures.append((test, reason))
1320
1346
1321 if self._options.first:
1347 if self._options.first:
1322 self.stop()
1348 self.stop()
1323 else:
1349 else:
1324 with iolock:
1350 with iolock:
1325 if reason == "timed out":
1351 if reason == "timed out":
1326 self.stream.write('t')
1352 self.stream.write('t')
1327 else:
1353 else:
1328 if not self._options.nodiff:
1354 if not self._options.nodiff:
1329 self.stream.write('\nERROR: %s output changed\n' % test)
1355 self.stream.write('\nERROR: %s output changed\n' % test)
1330 self.stream.write('!')
1356 self.stream.write('!')
1331
1357
1332 self.stream.flush()
1358 self.stream.flush()
1333
1359
1334 def addSuccess(self, test):
1360 def addSuccess(self, test):
1335 with iolock:
1361 with iolock:
1336 super(TestResult, self).addSuccess(test)
1362 super(TestResult, self).addSuccess(test)
1337 self.successes.append(test)
1363 self.successes.append(test)
1338
1364
1339 def addError(self, test, err):
1365 def addError(self, test, err):
1340 super(TestResult, self).addError(test, err)
1366 super(TestResult, self).addError(test, err)
1341 if self._options.first:
1367 if self._options.first:
1342 self.stop()
1368 self.stop()
1343
1369
1344 # Polyfill.
1370 # Polyfill.
1345 def addSkip(self, test, reason):
1371 def addSkip(self, test, reason):
1346 self.skipped.append((test, reason))
1372 self.skipped.append((test, reason))
1347 with iolock:
1373 with iolock:
1348 if self.showAll:
1374 if self.showAll:
1349 self.stream.writeln('skipped %s' % reason)
1375 self.stream.writeln('skipped %s' % reason)
1350 else:
1376 else:
1351 self.stream.write('s')
1377 self.stream.write('s')
1352 self.stream.flush()
1378 self.stream.flush()
1353
1379
1354 def addIgnore(self, test, reason):
1380 def addIgnore(self, test, reason):
1355 self.ignored.append((test, reason))
1381 self.ignored.append((test, reason))
1356 with iolock:
1382 with iolock:
1357 if self.showAll:
1383 if self.showAll:
1358 self.stream.writeln('ignored %s' % reason)
1384 self.stream.writeln('ignored %s' % reason)
1359 else:
1385 else:
1360 if reason not in ('not retesting', "doesn't match keyword"):
1386 if reason not in ('not retesting', "doesn't match keyword"):
1361 self.stream.write('i')
1387 self.stream.write('i')
1362 else:
1388 else:
1363 self.testsRun += 1
1389 self.testsRun += 1
1364 self.stream.flush()
1390 self.stream.flush()
1365
1391
1366 def addWarn(self, test, reason):
1392 def addWarn(self, test, reason):
1367 self.warned.append((test, reason))
1393 self.warned.append((test, reason))
1368
1394
1369 if self._options.first:
1395 if self._options.first:
1370 self.stop()
1396 self.stop()
1371
1397
1372 with iolock:
1398 with iolock:
1373 if self.showAll:
1399 if self.showAll:
1374 self.stream.writeln('warned %s' % reason)
1400 self.stream.writeln('warned %s' % reason)
1375 else:
1401 else:
1376 self.stream.write('~')
1402 self.stream.write('~')
1377 self.stream.flush()
1403 self.stream.flush()
1378
1404
1379 def addOutputMismatch(self, test, ret, got, expected):
1405 def addOutputMismatch(self, test, ret, got, expected):
1380 """Record a mismatch in test output for a particular test."""
1406 """Record a mismatch in test output for a particular test."""
1381 if self.shouldStop:
1407 if self.shouldStop:
1382 # don't print, some other test case already failed and
1408 # don't print, some other test case already failed and
1383 # printed, we're just stale and probably failed due to our
1409 # printed, we're just stale and probably failed due to our
1384 # temp dir getting cleaned up.
1410 # temp dir getting cleaned up.
1385 return
1411 return
1386
1412
1387 accepted = False
1413 accepted = False
1388 lines = []
1414 lines = []
1389
1415
1390 with iolock:
1416 with iolock:
1391 if self._options.nodiff:
1417 if self._options.nodiff:
1392 pass
1418 pass
1393 elif self._options.view:
1419 elif self._options.view:
1394 v = self._options.view
1420 v = self._options.view
1395 if PYTHON3:
1421 if PYTHON3:
1396 v = _bytespath(v)
1422 v = _bytespath(v)
1397 os.system(b"%s %s %s" %
1423 os.system(b"%s %s %s" %
1398 (v, test.refpath, test.errpath))
1424 (v, test.refpath, test.errpath))
1399 else:
1425 else:
1400 servefail, lines = getdiff(expected, got,
1426 servefail, lines = getdiff(expected, got,
1401 test.refpath, test.errpath)
1427 test.refpath, test.errpath)
1402 if servefail:
1428 if servefail:
1403 self.addFailure(
1429 self.addFailure(
1404 test,
1430 test,
1405 'server failed to start (HGPORT=%s)' % test._startport)
1431 'server failed to start (HGPORT=%s)' % test._startport)
1406 raise ReportedTest('server failed to start')
1432 raise ReportedTest('server failed to start')
1407 else:
1433 else:
1408 self.stream.write('\n')
1434 self.stream.write('\n')
1409 for line in lines:
1435 for line in lines:
1410 if PYTHON3:
1436 if PYTHON3:
1411 self.stream.flush()
1437 self.stream.flush()
1412 self.stream.buffer.write(line)
1438 self.stream.buffer.write(line)
1413 self.stream.buffer.flush()
1439 self.stream.buffer.flush()
1414 else:
1440 else:
1415 self.stream.write(line)
1441 self.stream.write(line)
1416 self.stream.flush()
1442 self.stream.flush()
1417
1443
1418 # handle interactive prompt without releasing iolock
1444 # handle interactive prompt without releasing iolock
1419 if self._options.interactive:
1445 if self._options.interactive:
1420 self.stream.write('Accept this change? [n] ')
1446 self.stream.write('Accept this change? [n] ')
1421 answer = sys.stdin.readline().strip()
1447 answer = sys.stdin.readline().strip()
1422 if answer.lower() in ('y', 'yes'):
1448 if answer.lower() in ('y', 'yes'):
1423 if test.name.endswith('.t'):
1449 if test.name.endswith('.t'):
1424 rename(test.errpath, test.path)
1450 rename(test.errpath, test.path)
1425 else:
1451 else:
1426 rename(test.errpath, '%s.out' % test.path)
1452 rename(test.errpath, '%s.out' % test.path)
1427 accepted = True
1453 accepted = True
1428 if not accepted:
1454 if not accepted:
1429 self.faildata[test.name] = b''.join(lines)
1455 self.faildata[test.name] = b''.join(lines)
1430
1456
1431 return accepted
1457 return accepted
1432
1458
1433 def startTest(self, test):
1459 def startTest(self, test):
1434 super(TestResult, self).startTest(test)
1460 super(TestResult, self).startTest(test)
1435
1461
1436 # os.times module computes the user time and system time spent by
1462 # os.times module computes the user time and system time spent by
1437 # child's processes along with real elapsed time taken by a process.
1463 # child's processes along with real elapsed time taken by a process.
1438 # This module has one limitation. It can only work for Linux user
1464 # This module has one limitation. It can only work for Linux user
1439 # and not for Windows.
1465 # and not for Windows.
1440 test.started = os.times()
1466 test.started = os.times()
1441 if self._firststarttime is None: # thread racy but irrelevant
1467 if self._firststarttime is None: # thread racy but irrelevant
1442 self._firststarttime = test.started[4]
1468 self._firststarttime = test.started[4]
1443
1469
1444 def stopTest(self, test, interrupted=False):
1470 def stopTest(self, test, interrupted=False):
1445 super(TestResult, self).stopTest(test)
1471 super(TestResult, self).stopTest(test)
1446
1472
1447 test.stopped = os.times()
1473 test.stopped = os.times()
1448
1474
1449 starttime = test.started
1475 starttime = test.started
1450 endtime = test.stopped
1476 endtime = test.stopped
1451 origin = self._firststarttime
1477 origin = self._firststarttime
1452 self.times.append((test.name,
1478 self.times.append((test.name,
1453 endtime[2] - starttime[2], # user space CPU time
1479 endtime[2] - starttime[2], # user space CPU time
1454 endtime[3] - starttime[3], # sys space CPU time
1480 endtime[3] - starttime[3], # sys space CPU time
1455 endtime[4] - starttime[4], # real time
1481 endtime[4] - starttime[4], # real time
1456 starttime[4] - origin, # start date in run context
1482 starttime[4] - origin, # start date in run context
1457 endtime[4] - origin, # end date in run context
1483 endtime[4] - origin, # end date in run context
1458 ))
1484 ))
1459
1485
1460 if interrupted:
1486 if interrupted:
1461 with iolock:
1487 with iolock:
1462 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1488 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1463 test.name, self.times[-1][3]))
1489 test.name, self.times[-1][3]))
1464
1490
1465 class TestSuite(unittest.TestSuite):
1491 class TestSuite(unittest.TestSuite):
1466 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1492 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1467
1493
1468 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1494 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1469 retest=False, keywords=None, loop=False, runs_per_test=1,
1495 retest=False, keywords=None, loop=False, runs_per_test=1,
1470 loadtest=None, showchannels=False,
1496 loadtest=None, showchannels=False,
1471 *args, **kwargs):
1497 *args, **kwargs):
1472 """Create a new instance that can run tests with a configuration.
1498 """Create a new instance that can run tests with a configuration.
1473
1499
1474 testdir specifies the directory where tests are executed from. This
1500 testdir specifies the directory where tests are executed from. This
1475 is typically the ``tests`` directory from Mercurial's source
1501 is typically the ``tests`` directory from Mercurial's source
1476 repository.
1502 repository.
1477
1503
1478 jobs specifies the number of jobs to run concurrently. Each test
1504 jobs specifies the number of jobs to run concurrently. Each test
1479 executes on its own thread. Tests actually spawn new processes, so
1505 executes on its own thread. Tests actually spawn new processes, so
1480 state mutation should not be an issue.
1506 state mutation should not be an issue.
1481
1507
1482 If there is only one job, it will use the main thread.
1508 If there is only one job, it will use the main thread.
1483
1509
1484 whitelist and blacklist denote tests that have been whitelisted and
1510 whitelist and blacklist denote tests that have been whitelisted and
1485 blacklisted, respectively. These arguments don't belong in TestSuite.
1511 blacklisted, respectively. These arguments don't belong in TestSuite.
1486 Instead, whitelist and blacklist should be handled by the thing that
1512 Instead, whitelist and blacklist should be handled by the thing that
1487 populates the TestSuite with tests. They are present to preserve
1513 populates the TestSuite with tests. They are present to preserve
1488 backwards compatible behavior which reports skipped tests as part
1514 backwards compatible behavior which reports skipped tests as part
1489 of the results.
1515 of the results.
1490
1516
1491 retest denotes whether to retest failed tests. This arguably belongs
1517 retest denotes whether to retest failed tests. This arguably belongs
1492 outside of TestSuite.
1518 outside of TestSuite.
1493
1519
1494 keywords denotes key words that will be used to filter which tests
1520 keywords denotes key words that will be used to filter which tests
1495 to execute. This arguably belongs outside of TestSuite.
1521 to execute. This arguably belongs outside of TestSuite.
1496
1522
1497 loop denotes whether to loop over tests forever.
1523 loop denotes whether to loop over tests forever.
1498 """
1524 """
1499 super(TestSuite, self).__init__(*args, **kwargs)
1525 super(TestSuite, self).__init__(*args, **kwargs)
1500
1526
1501 self._jobs = jobs
1527 self._jobs = jobs
1502 self._whitelist = whitelist
1528 self._whitelist = whitelist
1503 self._blacklist = blacklist
1529 self._blacklist = blacklist
1504 self._retest = retest
1530 self._retest = retest
1505 self._keywords = keywords
1531 self._keywords = keywords
1506 self._loop = loop
1532 self._loop = loop
1507 self._runs_per_test = runs_per_test
1533 self._runs_per_test = runs_per_test
1508 self._loadtest = loadtest
1534 self._loadtest = loadtest
1509 self._showchannels = showchannels
1535 self._showchannels = showchannels
1510
1536
1511 def run(self, result):
1537 def run(self, result):
1512 # We have a number of filters that need to be applied. We do this
1538 # We have a number of filters that need to be applied. We do this
1513 # here instead of inside Test because it makes the running logic for
1539 # here instead of inside Test because it makes the running logic for
1514 # Test simpler.
1540 # Test simpler.
1515 tests = []
1541 tests = []
1516 num_tests = [0]
1542 num_tests = [0]
1517 for test in self._tests:
1543 for test in self._tests:
1518 def get():
1544 def get():
1519 num_tests[0] += 1
1545 num_tests[0] += 1
1520 if getattr(test, 'should_reload', False):
1546 if getattr(test, 'should_reload', False):
1521 return self._loadtest(test.path, num_tests[0])
1547 return self._loadtest(test.path, num_tests[0])
1522 return test
1548 return test
1523 if not os.path.exists(test.path):
1549 if not os.path.exists(test.path):
1524 result.addSkip(test, "Doesn't exist")
1550 result.addSkip(test, "Doesn't exist")
1525 continue
1551 continue
1526
1552
1527 if not (self._whitelist and test.name in self._whitelist):
1553 if not (self._whitelist and test.name in self._whitelist):
1528 if self._blacklist and test.bname in self._blacklist:
1554 if self._blacklist and test.bname in self._blacklist:
1529 result.addSkip(test, 'blacklisted')
1555 result.addSkip(test, 'blacklisted')
1530 continue
1556 continue
1531
1557
1532 if self._retest and not os.path.exists(test.errpath):
1558 if self._retest and not os.path.exists(test.errpath):
1533 result.addIgnore(test, 'not retesting')
1559 result.addIgnore(test, 'not retesting')
1534 continue
1560 continue
1535
1561
1536 if self._keywords:
1562 if self._keywords:
1537 f = open(test.path, 'rb')
1563 f = open(test.path, 'rb')
1538 t = f.read().lower() + test.bname.lower()
1564 t = f.read().lower() + test.bname.lower()
1539 f.close()
1565 f.close()
1540 ignored = False
1566 ignored = False
1541 for k in self._keywords.lower().split():
1567 for k in self._keywords.lower().split():
1542 if k not in t:
1568 if k not in t:
1543 result.addIgnore(test, "doesn't match keyword")
1569 result.addIgnore(test, "doesn't match keyword")
1544 ignored = True
1570 ignored = True
1545 break
1571 break
1546
1572
1547 if ignored:
1573 if ignored:
1548 continue
1574 continue
1549 for _ in xrange(self._runs_per_test):
1575 for _ in xrange(self._runs_per_test):
1550 tests.append(get())
1576 tests.append(get())
1551
1577
1552 runtests = list(tests)
1578 runtests = list(tests)
1553 done = queue.Queue()
1579 done = queue.Queue()
1554 running = 0
1580 running = 0
1555
1581
1556 channels = [""] * self._jobs
1582 channels = [""] * self._jobs
1557
1583
1558 def job(test, result):
1584 def job(test, result):
1559 for n, v in enumerate(channels):
1585 for n, v in enumerate(channels):
1560 if not v:
1586 if not v:
1561 channel = n
1587 channel = n
1562 break
1588 break
1563 channels[channel] = "=" + test.name[5:].split(".")[0]
1589 channels[channel] = "=" + test.name[5:].split(".")[0]
1564 try:
1590 try:
1565 test(result)
1591 test(result)
1566 done.put(None)
1592 done.put(None)
1567 except KeyboardInterrupt:
1593 except KeyboardInterrupt:
1568 pass
1594 pass
1569 except: # re-raises
1595 except: # re-raises
1570 done.put(('!', test, 'run-test raised an error, see traceback'))
1596 done.put(('!', test, 'run-test raised an error, see traceback'))
1571 raise
1597 raise
1572 try:
1598 try:
1573 channels[channel] = ''
1599 channels[channel] = ''
1574 except IndexError:
1600 except IndexError:
1575 pass
1601 pass
1576
1602
1577 def stat():
1603 def stat():
1578 count = 0
1604 count = 0
1579 while channels:
1605 while channels:
1580 d = '\n%03s ' % count
1606 d = '\n%03s ' % count
1581 for n, v in enumerate(channels):
1607 for n, v in enumerate(channels):
1582 if v:
1608 if v:
1583 d += v[0]
1609 d += v[0]
1584 channels[n] = v[1:] or '.'
1610 channels[n] = v[1:] or '.'
1585 else:
1611 else:
1586 d += ' '
1612 d += ' '
1587 d += ' '
1613 d += ' '
1588 with iolock:
1614 with iolock:
1589 sys.stdout.write(d + ' ')
1615 sys.stdout.write(d + ' ')
1590 sys.stdout.flush()
1616 sys.stdout.flush()
1591 for x in xrange(10):
1617 for x in xrange(10):
1592 if channels:
1618 if channels:
1593 time.sleep(.1)
1619 time.sleep(.1)
1594 count += 1
1620 count += 1
1595
1621
1596 stoppedearly = False
1622 stoppedearly = False
1597
1623
1598 if self._showchannels:
1624 if self._showchannels:
1599 statthread = threading.Thread(target=stat, name="stat")
1625 statthread = threading.Thread(target=stat, name="stat")
1600 statthread.start()
1626 statthread.start()
1601
1627
1602 try:
1628 try:
1603 while tests or running:
1629 while tests or running:
1604 if not done.empty() or running == self._jobs or not tests:
1630 if not done.empty() or running == self._jobs or not tests:
1605 try:
1631 try:
1606 done.get(True, 1)
1632 done.get(True, 1)
1607 running -= 1
1633 running -= 1
1608 if result and result.shouldStop:
1634 if result and result.shouldStop:
1609 stoppedearly = True
1635 stoppedearly = True
1610 break
1636 break
1611 except queue.Empty:
1637 except queue.Empty:
1612 continue
1638 continue
1613 if tests and not running == self._jobs:
1639 if tests and not running == self._jobs:
1614 test = tests.pop(0)
1640 test = tests.pop(0)
1615 if self._loop:
1641 if self._loop:
1616 if getattr(test, 'should_reload', False):
1642 if getattr(test, 'should_reload', False):
1617 num_tests[0] += 1
1643 num_tests[0] += 1
1618 tests.append(
1644 tests.append(
1619 self._loadtest(test.name, num_tests[0]))
1645 self._loadtest(test.name, num_tests[0]))
1620 else:
1646 else:
1621 tests.append(test)
1647 tests.append(test)
1622 if self._jobs == 1:
1648 if self._jobs == 1:
1623 job(test, result)
1649 job(test, result)
1624 else:
1650 else:
1625 t = threading.Thread(target=job, name=test.name,
1651 t = threading.Thread(target=job, name=test.name,
1626 args=(test, result))
1652 args=(test, result))
1627 t.start()
1653 t.start()
1628 running += 1
1654 running += 1
1629
1655
1630 # If we stop early we still need to wait on started tests to
1656 # If we stop early we still need to wait on started tests to
1631 # finish. Otherwise, there is a race between the test completing
1657 # finish. Otherwise, there is a race between the test completing
1632 # and the test's cleanup code running. This could result in the
1658 # and the test's cleanup code running. This could result in the
1633 # test reporting incorrect.
1659 # test reporting incorrect.
1634 if stoppedearly:
1660 if stoppedearly:
1635 while running:
1661 while running:
1636 try:
1662 try:
1637 done.get(True, 1)
1663 done.get(True, 1)
1638 running -= 1
1664 running -= 1
1639 except queue.Empty:
1665 except queue.Empty:
1640 continue
1666 continue
1641 except KeyboardInterrupt:
1667 except KeyboardInterrupt:
1642 for test in runtests:
1668 for test in runtests:
1643 test.abort()
1669 test.abort()
1644
1670
1645 channels = []
1671 channels = []
1646
1672
1647 return result
1673 return result
1648
1674
1649 # Save the most recent 5 wall-clock runtimes of each test to a
1675 # Save the most recent 5 wall-clock runtimes of each test to a
1650 # human-readable text file named .testtimes. Tests are sorted
1676 # human-readable text file named .testtimes. Tests are sorted
1651 # alphabetically, while times for each test are listed from oldest to
1677 # alphabetically, while times for each test are listed from oldest to
1652 # newest.
1678 # newest.
1653
1679
1654 def loadtimes(testdir):
1680 def loadtimes(testdir):
1655 times = []
1681 times = []
1656 try:
1682 try:
1657 with open(os.path.join(testdir, b'.testtimes-')) as fp:
1683 with open(os.path.join(testdir, b'.testtimes-')) as fp:
1658 for line in fp:
1684 for line in fp:
1659 ts = line.split()
1685 ts = line.split()
1660 times.append((ts[0], [float(t) for t in ts[1:]]))
1686 times.append((ts[0], [float(t) for t in ts[1:]]))
1661 except IOError as err:
1687 except IOError as err:
1662 if err.errno != errno.ENOENT:
1688 if err.errno != errno.ENOENT:
1663 raise
1689 raise
1664 return times
1690 return times
1665
1691
1666 def savetimes(testdir, result):
1692 def savetimes(testdir, result):
1667 saved = dict(loadtimes(testdir))
1693 saved = dict(loadtimes(testdir))
1668 maxruns = 5
1694 maxruns = 5
1669 skipped = set([str(t[0]) for t in result.skipped])
1695 skipped = set([str(t[0]) for t in result.skipped])
1670 for tdata in result.times:
1696 for tdata in result.times:
1671 test, real = tdata[0], tdata[3]
1697 test, real = tdata[0], tdata[3]
1672 if test not in skipped:
1698 if test not in skipped:
1673 ts = saved.setdefault(test, [])
1699 ts = saved.setdefault(test, [])
1674 ts.append(real)
1700 ts.append(real)
1675 ts[:] = ts[-maxruns:]
1701 ts[:] = ts[-maxruns:]
1676
1702
1677 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
1703 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
1678 dir=testdir, text=True)
1704 dir=testdir, text=True)
1679 with os.fdopen(fd, 'w') as fp:
1705 with os.fdopen(fd, 'w') as fp:
1680 for name, ts in sorted(saved.items()):
1706 for name, ts in sorted(saved.items()):
1681 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1707 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1682 timepath = os.path.join(testdir, b'.testtimes')
1708 timepath = os.path.join(testdir, b'.testtimes')
1683 try:
1709 try:
1684 os.unlink(timepath)
1710 os.unlink(timepath)
1685 except OSError:
1711 except OSError:
1686 pass
1712 pass
1687 try:
1713 try:
1688 os.rename(tmpname, timepath)
1714 os.rename(tmpname, timepath)
1689 except OSError:
1715 except OSError:
1690 pass
1716 pass
1691
1717
1692 class TextTestRunner(unittest.TextTestRunner):
1718 class TextTestRunner(unittest.TextTestRunner):
1693 """Custom unittest test runner that uses appropriate settings."""
1719 """Custom unittest test runner that uses appropriate settings."""
1694
1720
1695 def __init__(self, runner, *args, **kwargs):
1721 def __init__(self, runner, *args, **kwargs):
1696 super(TextTestRunner, self).__init__(*args, **kwargs)
1722 super(TextTestRunner, self).__init__(*args, **kwargs)
1697
1723
1698 self._runner = runner
1724 self._runner = runner
1699
1725
1700 def run(self, test):
1726 def run(self, test):
1701 result = TestResult(self._runner.options, self.stream,
1727 result = TestResult(self._runner.options, self.stream,
1702 self.descriptions, self.verbosity)
1728 self.descriptions, self.verbosity)
1703
1729
1704 test(result)
1730 test(result)
1705
1731
1706 failed = len(result.failures)
1732 failed = len(result.failures)
1707 warned = len(result.warned)
1733 warned = len(result.warned)
1708 skipped = len(result.skipped)
1734 skipped = len(result.skipped)
1709 ignored = len(result.ignored)
1735 ignored = len(result.ignored)
1710
1736
1711 with iolock:
1737 with iolock:
1712 self.stream.writeln('')
1738 self.stream.writeln('')
1713
1739
1714 if not self._runner.options.noskips:
1740 if not self._runner.options.noskips:
1715 for test, msg in result.skipped:
1741 for test, msg in result.skipped:
1716 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1742 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1717 for test, msg in result.warned:
1743 for test, msg in result.warned:
1718 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1744 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1719 for test, msg in result.failures:
1745 for test, msg in result.failures:
1720 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1746 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1721 for test, msg in result.errors:
1747 for test, msg in result.errors:
1722 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1748 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1723
1749
1724 if self._runner.options.xunit:
1750 if self._runner.options.xunit:
1725 with open(self._runner.options.xunit, 'wb') as xuf:
1751 with open(self._runner.options.xunit, 'wb') as xuf:
1726 timesd = dict((t[0], t[3]) for t in result.times)
1752 timesd = dict((t[0], t[3]) for t in result.times)
1727 doc = minidom.Document()
1753 doc = minidom.Document()
1728 s = doc.createElement('testsuite')
1754 s = doc.createElement('testsuite')
1729 s.setAttribute('name', 'run-tests')
1755 s.setAttribute('name', 'run-tests')
1730 s.setAttribute('tests', str(result.testsRun))
1756 s.setAttribute('tests', str(result.testsRun))
1731 s.setAttribute('errors', "0") # TODO
1757 s.setAttribute('errors', "0") # TODO
1732 s.setAttribute('failures', str(failed))
1758 s.setAttribute('failures', str(failed))
1733 s.setAttribute('skipped', str(skipped + ignored))
1759 s.setAttribute('skipped', str(skipped + ignored))
1734 doc.appendChild(s)
1760 doc.appendChild(s)
1735 for tc in result.successes:
1761 for tc in result.successes:
1736 t = doc.createElement('testcase')
1762 t = doc.createElement('testcase')
1737 t.setAttribute('name', tc.name)
1763 t.setAttribute('name', tc.name)
1738 t.setAttribute('time', '%.3f' % timesd[tc.name])
1764 t.setAttribute('time', '%.3f' % timesd[tc.name])
1739 s.appendChild(t)
1765 s.appendChild(t)
1740 for tc, err in sorted(result.faildata.items()):
1766 for tc, err in sorted(result.faildata.items()):
1741 t = doc.createElement('testcase')
1767 t = doc.createElement('testcase')
1742 t.setAttribute('name', tc)
1768 t.setAttribute('name', tc)
1743 t.setAttribute('time', '%.3f' % timesd[tc])
1769 t.setAttribute('time', '%.3f' % timesd[tc])
1744 # createCDATASection expects a unicode or it will
1770 # createCDATASection expects a unicode or it will
1745 # convert using default conversion rules, which will
1771 # convert using default conversion rules, which will
1746 # fail if string isn't ASCII.
1772 # fail if string isn't ASCII.
1747 err = cdatasafe(err).decode('utf-8', 'replace')
1773 err = cdatasafe(err).decode('utf-8', 'replace')
1748 cd = doc.createCDATASection(err)
1774 cd = doc.createCDATASection(err)
1749 t.appendChild(cd)
1775 t.appendChild(cd)
1750 s.appendChild(t)
1776 s.appendChild(t)
1751 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1777 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1752
1778
1753 if self._runner.options.json:
1779 if self._runner.options.json:
1754 jsonpath = os.path.join(self._runner._testdir, b'report.json')
1780 jsonpath = os.path.join(self._runner._testdir, b'report.json')
1755 with open(jsonpath, 'w') as fp:
1781 with open(jsonpath, 'w') as fp:
1756 timesd = {}
1782 timesd = {}
1757 for tdata in result.times:
1783 for tdata in result.times:
1758 test = tdata[0]
1784 test = tdata[0]
1759 timesd[test] = tdata[1:]
1785 timesd[test] = tdata[1:]
1760
1786
1761 outcome = {}
1787 outcome = {}
1762 groups = [('success', ((tc, None)
1788 groups = [('success', ((tc, None)
1763 for tc in result.successes)),
1789 for tc in result.successes)),
1764 ('failure', result.failures),
1790 ('failure', result.failures),
1765 ('skip', result.skipped)]
1791 ('skip', result.skipped)]
1766 for res, testcases in groups:
1792 for res, testcases in groups:
1767 for tc, __ in testcases:
1793 for tc, __ in testcases:
1768 if tc.name in timesd:
1794 if tc.name in timesd:
1769 diff = result.faildata.get(tc.name, b'')
1795 diff = result.faildata.get(tc.name, b'')
1770 tres = {'result': res,
1796 tres = {'result': res,
1771 'time': ('%0.3f' % timesd[tc.name][2]),
1797 'time': ('%0.3f' % timesd[tc.name][2]),
1772 'cuser': ('%0.3f' % timesd[tc.name][0]),
1798 'cuser': ('%0.3f' % timesd[tc.name][0]),
1773 'csys': ('%0.3f' % timesd[tc.name][1]),
1799 'csys': ('%0.3f' % timesd[tc.name][1]),
1774 'start': ('%0.3f' % timesd[tc.name][3]),
1800 'start': ('%0.3f' % timesd[tc.name][3]),
1775 'end': ('%0.3f' % timesd[tc.name][4]),
1801 'end': ('%0.3f' % timesd[tc.name][4]),
1776 'diff': diff.decode('unicode_escape'),
1802 'diff': diff.decode('unicode_escape'),
1777 }
1803 }
1778 else:
1804 else:
1779 # blacklisted test
1805 # blacklisted test
1780 tres = {'result': res}
1806 tres = {'result': res}
1781
1807
1782 outcome[tc.name] = tres
1808 outcome[tc.name] = tres
1783 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1809 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1784 fp.writelines(("testreport =", jsonout))
1810 fp.writelines(("testreport =", jsonout))
1785
1811
1786 self._runner._checkhglib('Tested')
1812 self._runner._checkhglib('Tested')
1787
1813
1788 savetimes(self._runner._testdir, result)
1814 savetimes(self._runner._testdir, result)
1789 self.stream.writeln(
1815 self.stream.writeln(
1790 '# Ran %d tests, %d skipped, %d warned, %d failed.'
1816 '# Ran %d tests, %d skipped, %d warned, %d failed.'
1791 % (result.testsRun,
1817 % (result.testsRun,
1792 skipped + ignored, warned, failed))
1818 skipped + ignored, warned, failed))
1793 if failed:
1819 if failed:
1794 self.stream.writeln('python hash seed: %s' %
1820 self.stream.writeln('python hash seed: %s' %
1795 os.environ['PYTHONHASHSEED'])
1821 os.environ['PYTHONHASHSEED'])
1796 if self._runner.options.time:
1822 if self._runner.options.time:
1797 self.printtimes(result.times)
1823 self.printtimes(result.times)
1798
1824
1799 return result
1825 return result
1800
1826
1801 def printtimes(self, times):
1827 def printtimes(self, times):
1802 # iolock held by run
1828 # iolock held by run
1803 self.stream.writeln('# Producing time report')
1829 self.stream.writeln('# Producing time report')
1804 times.sort(key=lambda t: (t[3]))
1830 times.sort(key=lambda t: (t[3]))
1805 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
1831 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
1806 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
1832 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
1807 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
1833 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
1808 for tdata in times:
1834 for tdata in times:
1809 test = tdata[0]
1835 test = tdata[0]
1810 cuser, csys, real, start, end = tdata[1:6]
1836 cuser, csys, real, start, end = tdata[1:6]
1811 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
1837 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
1812
1838
1813 class TestRunner(object):
1839 class TestRunner(object):
1814 """Holds context for executing tests.
1840 """Holds context for executing tests.
1815
1841
1816 Tests rely on a lot of state. This object holds it for them.
1842 Tests rely on a lot of state. This object holds it for them.
1817 """
1843 """
1818
1844
1819 # Programs required to run tests.
1845 # Programs required to run tests.
1820 REQUIREDTOOLS = [
1846 REQUIREDTOOLS = [
1821 os.path.basename(_bytespath(sys.executable)),
1847 os.path.basename(_bytespath(sys.executable)),
1822 b'diff',
1848 b'diff',
1823 b'grep',
1849 b'grep',
1824 b'unzip',
1850 b'unzip',
1825 b'gunzip',
1851 b'gunzip',
1826 b'bunzip2',
1852 b'bunzip2',
1827 b'sed',
1853 b'sed',
1828 ]
1854 ]
1829
1855
1830 # Maps file extensions to test class.
1856 # Maps file extensions to test class.
1831 TESTTYPES = [
1857 TESTTYPES = [
1832 (b'.py', PythonTest),
1858 (b'.py', PythonTest),
1833 (b'.t', TTest),
1859 (b'.t', TTest),
1834 ]
1860 ]
1835
1861
1836 def __init__(self):
1862 def __init__(self):
1837 self.options = None
1863 self.options = None
1838 self._hgroot = None
1864 self._hgroot = None
1839 self._testdir = None
1865 self._testdir = None
1840 self._hgtmp = None
1866 self._hgtmp = None
1841 self._installdir = None
1867 self._installdir = None
1842 self._bindir = None
1868 self._bindir = None
1843 self._tmpbinddir = None
1869 self._tmpbinddir = None
1844 self._pythondir = None
1870 self._pythondir = None
1845 self._coveragefile = None
1871 self._coveragefile = None
1846 self._createdfiles = []
1872 self._createdfiles = []
1847 self._hgcommand = None
1873 self._hgcommand = None
1848 self._hgpath = None
1874 self._hgpath = None
1849 self._chgsockdir = None
1875 self._chgsockdir = None
1850 self._portoffset = 0
1876 self._portoffset = 0
1851 self._ports = {}
1877 self._ports = {}
1852
1878
1853 def run(self, args, parser=None):
1879 def run(self, args, parser=None):
1854 """Run the test suite."""
1880 """Run the test suite."""
1855 oldmask = os.umask(0o22)
1881 oldmask = os.umask(0o22)
1856 try:
1882 try:
1857 parser = parser or getparser()
1883 parser = parser or getparser()
1858 options, args = parseargs(args, parser)
1884 options, args = parseargs(args, parser)
1859 # positional arguments are paths to test files to run, so
1885 # positional arguments are paths to test files to run, so
1860 # we make sure they're all bytestrings
1886 # we make sure they're all bytestrings
1861 args = [_bytespath(a) for a in args]
1887 args = [_bytespath(a) for a in args]
1862 self.options = options
1888 self.options = options
1863
1889
1864 self._checktools()
1890 self._checktools()
1865 tests = self.findtests(args)
1891 tests = self.findtests(args)
1866 if options.profile_runner:
1892 if options.profile_runner:
1867 import statprof
1893 import statprof
1868 statprof.start()
1894 statprof.start()
1869 result = self._run(tests)
1895 result = self._run(tests)
1870 if options.profile_runner:
1896 if options.profile_runner:
1871 statprof.stop()
1897 statprof.stop()
1872 statprof.display()
1898 statprof.display()
1873 return result
1899 return result
1874
1900
1875 finally:
1901 finally:
1876 os.umask(oldmask)
1902 os.umask(oldmask)
1877
1903
1878 def _run(self, tests):
1904 def _run(self, tests):
1879 if self.options.random:
1905 if self.options.random:
1880 random.shuffle(tests)
1906 random.shuffle(tests)
1881 else:
1907 else:
1882 # keywords for slow tests
1908 # keywords for slow tests
1883 slow = {b'svn': 10,
1909 slow = {b'svn': 10,
1884 b'cvs': 10,
1910 b'cvs': 10,
1885 b'hghave': 10,
1911 b'hghave': 10,
1886 b'largefiles-update': 10,
1912 b'largefiles-update': 10,
1887 b'run-tests': 10,
1913 b'run-tests': 10,
1888 b'corruption': 10,
1914 b'corruption': 10,
1889 b'race': 10,
1915 b'race': 10,
1890 b'i18n': 10,
1916 b'i18n': 10,
1891 b'check': 100,
1917 b'check': 100,
1892 b'gendoc': 100,
1918 b'gendoc': 100,
1893 b'contrib-perf': 200,
1919 b'contrib-perf': 200,
1894 }
1920 }
1895 perf = {}
1921 perf = {}
1896 def sortkey(f):
1922 def sortkey(f):
1897 # run largest tests first, as they tend to take the longest
1923 # run largest tests first, as they tend to take the longest
1898 try:
1924 try:
1899 return perf[f]
1925 return perf[f]
1900 except KeyError:
1926 except KeyError:
1901 try:
1927 try:
1902 val = -os.stat(f).st_size
1928 val = -os.stat(f).st_size
1903 except OSError as e:
1929 except OSError as e:
1904 if e.errno != errno.ENOENT:
1930 if e.errno != errno.ENOENT:
1905 raise
1931 raise
1906 perf[f] = -1e9 # file does not exist, tell early
1932 perf[f] = -1e9 # file does not exist, tell early
1907 return -1e9
1933 return -1e9
1908 for kw, mul in slow.items():
1934 for kw, mul in slow.items():
1909 if kw in f:
1935 if kw in f:
1910 val *= mul
1936 val *= mul
1911 if f.endswith(b'.py'):
1937 if f.endswith(b'.py'):
1912 val /= 10.0
1938 val /= 10.0
1913 perf[f] = val / 1000.0
1939 perf[f] = val / 1000.0
1914 return perf[f]
1940 return perf[f]
1915 tests.sort(key=sortkey)
1941 tests.sort(key=sortkey)
1916
1942
1917 self._testdir = osenvironb[b'TESTDIR'] = getattr(
1943 self._testdir = osenvironb[b'TESTDIR'] = getattr(
1918 os, 'getcwdb', os.getcwd)()
1944 os, 'getcwdb', os.getcwd)()
1919
1945
1920 if 'PYTHONHASHSEED' not in os.environ:
1946 if 'PYTHONHASHSEED' not in os.environ:
1921 # use a random python hash seed all the time
1947 # use a random python hash seed all the time
1922 # we do the randomness ourself to know what seed is used
1948 # we do the randomness ourself to know what seed is used
1923 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1949 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1924
1950
1925 if self.options.tmpdir:
1951 if self.options.tmpdir:
1926 self.options.keep_tmpdir = True
1952 self.options.keep_tmpdir = True
1927 tmpdir = _bytespath(self.options.tmpdir)
1953 tmpdir = _bytespath(self.options.tmpdir)
1928 if os.path.exists(tmpdir):
1954 if os.path.exists(tmpdir):
1929 # Meaning of tmpdir has changed since 1.3: we used to create
1955 # Meaning of tmpdir has changed since 1.3: we used to create
1930 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1956 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1931 # tmpdir already exists.
1957 # tmpdir already exists.
1932 print("error: temp dir %r already exists" % tmpdir)
1958 print("error: temp dir %r already exists" % tmpdir)
1933 return 1
1959 return 1
1934
1960
1935 # Automatically removing tmpdir sounds convenient, but could
1961 # Automatically removing tmpdir sounds convenient, but could
1936 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1962 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1937 # or "--tmpdir=$HOME".
1963 # or "--tmpdir=$HOME".
1938 #vlog("# Removing temp dir", tmpdir)
1964 #vlog("# Removing temp dir", tmpdir)
1939 #shutil.rmtree(tmpdir)
1965 #shutil.rmtree(tmpdir)
1940 os.makedirs(tmpdir)
1966 os.makedirs(tmpdir)
1941 else:
1967 else:
1942 d = None
1968 d = None
1943 if os.name == 'nt':
1969 if os.name == 'nt':
1944 # without this, we get the default temp dir location, but
1970 # without this, we get the default temp dir location, but
1945 # in all lowercase, which causes troubles with paths (issue3490)
1971 # in all lowercase, which causes troubles with paths (issue3490)
1946 d = osenvironb.get(b'TMP', None)
1972 d = osenvironb.get(b'TMP', None)
1947 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
1973 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
1948
1974
1949 self._hgtmp = osenvironb[b'HGTMP'] = (
1975 self._hgtmp = osenvironb[b'HGTMP'] = (
1950 os.path.realpath(tmpdir))
1976 os.path.realpath(tmpdir))
1951
1977
1952 if self.options.with_hg:
1978 if self.options.with_hg:
1953 self._installdir = None
1979 self._installdir = None
1954 whg = self.options.with_hg
1980 whg = self.options.with_hg
1955 self._bindir = os.path.dirname(os.path.realpath(whg))
1981 self._bindir = os.path.dirname(os.path.realpath(whg))
1956 assert isinstance(self._bindir, bytes)
1982 assert isinstance(self._bindir, bytes)
1957 self._hgcommand = os.path.basename(whg)
1983 self._hgcommand = os.path.basename(whg)
1958 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
1984 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
1959 os.makedirs(self._tmpbindir)
1985 os.makedirs(self._tmpbindir)
1960
1986
1961 # This looks redundant with how Python initializes sys.path from
1987 # This looks redundant with how Python initializes sys.path from
1962 # the location of the script being executed. Needed because the
1988 # the location of the script being executed. Needed because the
1963 # "hg" specified by --with-hg is not the only Python script
1989 # "hg" specified by --with-hg is not the only Python script
1964 # executed in the test suite that needs to import 'mercurial'
1990 # executed in the test suite that needs to import 'mercurial'
1965 # ... which means it's not really redundant at all.
1991 # ... which means it's not really redundant at all.
1966 self._pythondir = self._bindir
1992 self._pythondir = self._bindir
1967 else:
1993 else:
1968 self._installdir = os.path.join(self._hgtmp, b"install")
1994 self._installdir = os.path.join(self._hgtmp, b"install")
1969 self._bindir = os.path.join(self._installdir, b"bin")
1995 self._bindir = os.path.join(self._installdir, b"bin")
1970 self._hgcommand = b'hg'
1996 self._hgcommand = b'hg'
1971 self._tmpbindir = self._bindir
1997 self._tmpbindir = self._bindir
1972 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
1998 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
1973
1999
1974 # set up crafted chg environment, then replace "hg" command by "chg"
2000 # set up crafted chg environment, then replace "hg" command by "chg"
1975 chgbindir = self._bindir
2001 chgbindir = self._bindir
1976 if self.options.chg or self.options.with_chg:
2002 if self.options.chg or self.options.with_chg:
1977 self._chgsockdir = d = os.path.join(self._hgtmp, b'chgsock')
2003 self._chgsockdir = d = os.path.join(self._hgtmp, b'chgsock')
1978 os.mkdir(d)
2004 os.mkdir(d)
1979 osenvironb[b'CHGSOCKNAME'] = os.path.join(d, b"server")
2005 osenvironb[b'CHGSOCKNAME'] = os.path.join(d, b"server")
1980 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2006 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
1981 if self.options.chg:
2007 if self.options.chg:
1982 self._hgcommand = b'chg'
2008 self._hgcommand = b'chg'
1983 elif self.options.with_chg:
2009 elif self.options.with_chg:
1984 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2010 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
1985 self._hgcommand = os.path.basename(self.options.with_chg)
2011 self._hgcommand = os.path.basename(self.options.with_chg)
1986
2012
1987 osenvironb[b"BINDIR"] = self._bindir
2013 osenvironb[b"BINDIR"] = self._bindir
1988 osenvironb[b"PYTHON"] = PYTHON
2014 osenvironb[b"PYTHON"] = PYTHON
1989
2015
2016 if self.options.with_python3:
2017 osenvironb[b'PYTHON3'] = self.options.with_python3
2018
1990 fileb = _bytespath(__file__)
2019 fileb = _bytespath(__file__)
1991 runtestdir = os.path.abspath(os.path.dirname(fileb))
2020 runtestdir = os.path.abspath(os.path.dirname(fileb))
1992 osenvironb[b'RUNTESTDIR'] = runtestdir
2021 osenvironb[b'RUNTESTDIR'] = runtestdir
1993 if PYTHON3:
2022 if PYTHON3:
1994 sepb = _bytespath(os.pathsep)
2023 sepb = _bytespath(os.pathsep)
1995 else:
2024 else:
1996 sepb = os.pathsep
2025 sepb = os.pathsep
1997 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2026 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
1998 if os.path.islink(__file__):
2027 if os.path.islink(__file__):
1999 # test helper will likely be at the end of the symlink
2028 # test helper will likely be at the end of the symlink
2000 realfile = os.path.realpath(fileb)
2029 realfile = os.path.realpath(fileb)
2001 realdir = os.path.abspath(os.path.dirname(realfile))
2030 realdir = os.path.abspath(os.path.dirname(realfile))
2002 path.insert(2, realdir)
2031 path.insert(2, realdir)
2003 if chgbindir != self._bindir:
2032 if chgbindir != self._bindir:
2004 path.insert(1, chgbindir)
2033 path.insert(1, chgbindir)
2005 if self._testdir != runtestdir:
2034 if self._testdir != runtestdir:
2006 path = [self._testdir] + path
2035 path = [self._testdir] + path
2007 if self._tmpbindir != self._bindir:
2036 if self._tmpbindir != self._bindir:
2008 path = [self._tmpbindir] + path
2037 path = [self._tmpbindir] + path
2009 osenvironb[b"PATH"] = sepb.join(path)
2038 osenvironb[b"PATH"] = sepb.join(path)
2010
2039
2011 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2040 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2012 # can run .../tests/run-tests.py test-foo where test-foo
2041 # can run .../tests/run-tests.py test-foo where test-foo
2013 # adds an extension to HGRC. Also include run-test.py directory to
2042 # adds an extension to HGRC. Also include run-test.py directory to
2014 # import modules like heredoctest.
2043 # import modules like heredoctest.
2015 pypath = [self._pythondir, self._testdir, runtestdir]
2044 pypath = [self._pythondir, self._testdir, runtestdir]
2016 # We have to augment PYTHONPATH, rather than simply replacing
2045 # We have to augment PYTHONPATH, rather than simply replacing
2017 # it, in case external libraries are only available via current
2046 # it, in case external libraries are only available via current
2018 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2047 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2019 # are in /opt/subversion.)
2048 # are in /opt/subversion.)
2020 oldpypath = osenvironb.get(IMPL_PATH)
2049 oldpypath = osenvironb.get(IMPL_PATH)
2021 if oldpypath:
2050 if oldpypath:
2022 pypath.append(oldpypath)
2051 pypath.append(oldpypath)
2023 osenvironb[IMPL_PATH] = sepb.join(pypath)
2052 osenvironb[IMPL_PATH] = sepb.join(pypath)
2024
2053
2025 if self.options.pure:
2054 if self.options.pure:
2026 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2055 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2027
2056
2028 if self.options.allow_slow_tests:
2057 if self.options.allow_slow_tests:
2029 os.environ["HGTEST_SLOW"] = "slow"
2058 os.environ["HGTEST_SLOW"] = "slow"
2030 elif 'HGTEST_SLOW' in os.environ:
2059 elif 'HGTEST_SLOW' in os.environ:
2031 del os.environ['HGTEST_SLOW']
2060 del os.environ['HGTEST_SLOW']
2032
2061
2033 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2062 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2034
2063
2035 vlog("# Using TESTDIR", self._testdir)
2064 vlog("# Using TESTDIR", self._testdir)
2036 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2065 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2037 vlog("# Using HGTMP", self._hgtmp)
2066 vlog("# Using HGTMP", self._hgtmp)
2038 vlog("# Using PATH", os.environ["PATH"])
2067 vlog("# Using PATH", os.environ["PATH"])
2039 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2068 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2040
2069
2041 try:
2070 try:
2042 return self._runtests(tests) or 0
2071 return self._runtests(tests) or 0
2043 finally:
2072 finally:
2044 time.sleep(.1)
2073 time.sleep(.1)
2045 self._cleanup()
2074 self._cleanup()
2046
2075
2047 def findtests(self, args):
2076 def findtests(self, args):
2048 """Finds possible test files from arguments.
2077 """Finds possible test files from arguments.
2049
2078
2050 If you wish to inject custom tests into the test harness, this would
2079 If you wish to inject custom tests into the test harness, this would
2051 be a good function to monkeypatch or override in a derived class.
2080 be a good function to monkeypatch or override in a derived class.
2052 """
2081 """
2053 if not args:
2082 if not args:
2054 if self.options.changed:
2083 if self.options.changed:
2055 proc = Popen4('hg st --rev "%s" -man0 .' %
2084 proc = Popen4('hg st --rev "%s" -man0 .' %
2056 self.options.changed, None, 0)
2085 self.options.changed, None, 0)
2057 stdout, stderr = proc.communicate()
2086 stdout, stderr = proc.communicate()
2058 args = stdout.strip(b'\0').split(b'\0')
2087 args = stdout.strip(b'\0').split(b'\0')
2059 else:
2088 else:
2060 args = os.listdir(b'.')
2089 args = os.listdir(b'.')
2061
2090
2062 return [t for t in args
2091 return [t for t in args
2063 if os.path.basename(t).startswith(b'test-')
2092 if os.path.basename(t).startswith(b'test-')
2064 and (t.endswith(b'.py') or t.endswith(b'.t'))]
2093 and (t.endswith(b'.py') or t.endswith(b'.t'))]
2065
2094
2066 def _runtests(self, tests):
2095 def _runtests(self, tests):
2067 try:
2096 try:
2068 if self._installdir:
2097 if self._installdir:
2069 self._installhg()
2098 self._installhg()
2070 self._checkhglib("Testing")
2099 self._checkhglib("Testing")
2071 else:
2100 else:
2072 self._usecorrectpython()
2101 self._usecorrectpython()
2073 if self.options.chg:
2102 if self.options.chg:
2074 assert self._installdir
2103 assert self._installdir
2075 self._installchg()
2104 self._installchg()
2076
2105
2077 if self.options.restart:
2106 if self.options.restart:
2078 orig = list(tests)
2107 orig = list(tests)
2079 while tests:
2108 while tests:
2080 if os.path.exists(tests[0] + ".err"):
2109 if os.path.exists(tests[0] + ".err"):
2081 break
2110 break
2082 tests.pop(0)
2111 tests.pop(0)
2083 if not tests:
2112 if not tests:
2084 print("running all tests")
2113 print("running all tests")
2085 tests = orig
2114 tests = orig
2086
2115
2087 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
2116 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
2088
2117
2089 failed = False
2118 failed = False
2090 warned = False
2119 warned = False
2091 kws = self.options.keywords
2120 kws = self.options.keywords
2092 if kws is not None and PYTHON3:
2121 if kws is not None and PYTHON3:
2093 kws = kws.encode('utf-8')
2122 kws = kws.encode('utf-8')
2094
2123
2095 suite = TestSuite(self._testdir,
2124 suite = TestSuite(self._testdir,
2096 jobs=self.options.jobs,
2125 jobs=self.options.jobs,
2097 whitelist=self.options.whitelisted,
2126 whitelist=self.options.whitelisted,
2098 blacklist=self.options.blacklist,
2127 blacklist=self.options.blacklist,
2099 retest=self.options.retest,
2128 retest=self.options.retest,
2100 keywords=kws,
2129 keywords=kws,
2101 loop=self.options.loop,
2130 loop=self.options.loop,
2102 runs_per_test=self.options.runs_per_test,
2131 runs_per_test=self.options.runs_per_test,
2103 showchannels=self.options.showchannels,
2132 showchannels=self.options.showchannels,
2104 tests=tests, loadtest=self._gettest)
2133 tests=tests, loadtest=self._gettest)
2105 verbosity = 1
2134 verbosity = 1
2106 if self.options.verbose:
2135 if self.options.verbose:
2107 verbosity = 2
2136 verbosity = 2
2108 runner = TextTestRunner(self, verbosity=verbosity)
2137 runner = TextTestRunner(self, verbosity=verbosity)
2109 result = runner.run(suite)
2138 result = runner.run(suite)
2110
2139
2111 if result.failures:
2140 if result.failures:
2112 failed = True
2141 failed = True
2113 if result.warned:
2142 if result.warned:
2114 warned = True
2143 warned = True
2115
2144
2116 if self.options.anycoverage:
2145 if self.options.anycoverage:
2117 self._outputcoverage()
2146 self._outputcoverage()
2118 except KeyboardInterrupt:
2147 except KeyboardInterrupt:
2119 failed = True
2148 failed = True
2120 print("\ninterrupted!")
2149 print("\ninterrupted!")
2121
2150
2122 if failed:
2151 if failed:
2123 return 1
2152 return 1
2124 if warned:
2153 if warned:
2125 return 80
2154 return 80
2126
2155
2127 def _getport(self, count):
2156 def _getport(self, count):
2128 port = self._ports.get(count) # do we have a cached entry?
2157 port = self._ports.get(count) # do we have a cached entry?
2129 if port is None:
2158 if port is None:
2130 portneeded = 3
2159 portneeded = 3
2131 # above 100 tries we just give up and let test reports failure
2160 # above 100 tries we just give up and let test reports failure
2132 for tries in xrange(100):
2161 for tries in xrange(100):
2133 allfree = True
2162 allfree = True
2134 port = self.options.port + self._portoffset
2163 port = self.options.port + self._portoffset
2135 for idx in xrange(portneeded):
2164 for idx in xrange(portneeded):
2136 if not checkportisavailable(port + idx):
2165 if not checkportisavailable(port + idx):
2137 allfree = False
2166 allfree = False
2138 break
2167 break
2139 self._portoffset += portneeded
2168 self._portoffset += portneeded
2140 if allfree:
2169 if allfree:
2141 break
2170 break
2142 self._ports[count] = port
2171 self._ports[count] = port
2143 return port
2172 return port
2144
2173
2145 def _gettest(self, test, count):
2174 def _gettest(self, test, count):
2146 """Obtain a Test by looking at its filename.
2175 """Obtain a Test by looking at its filename.
2147
2176
2148 Returns a Test instance. The Test may not be runnable if it doesn't
2177 Returns a Test instance. The Test may not be runnable if it doesn't
2149 map to a known type.
2178 map to a known type.
2150 """
2179 """
2151 lctest = test.lower()
2180 lctest = test.lower()
2152 testcls = Test
2181 testcls = Test
2153
2182
2154 for ext, cls in self.TESTTYPES:
2183 for ext, cls in self.TESTTYPES:
2155 if lctest.endswith(ext):
2184 if lctest.endswith(ext):
2156 testcls = cls
2185 testcls = cls
2157 break
2186 break
2158
2187
2159 refpath = os.path.join(self._testdir, test)
2188 refpath = os.path.join(self._testdir, test)
2160 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2189 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2161
2190
2162 t = testcls(refpath, tmpdir,
2191 t = testcls(refpath, tmpdir,
2163 keeptmpdir=self.options.keep_tmpdir,
2192 keeptmpdir=self.options.keep_tmpdir,
2164 debug=self.options.debug,
2193 debug=self.options.debug,
2165 timeout=self.options.timeout,
2194 timeout=self.options.timeout,
2166 startport=self._getport(count),
2195 startport=self._getport(count),
2167 extraconfigopts=self.options.extra_config_opt,
2196 extraconfigopts=self.options.extra_config_opt,
2168 py3kwarnings=self.options.py3k_warnings,
2197 py3kwarnings=self.options.py3k_warnings,
2169 shell=self.options.shell,
2198 shell=self.options.shell,
2170 hgcommand=self._hgcommand)
2199 hgcommand=self._hgcommand)
2171 t.should_reload = True
2200 t.should_reload = True
2172 return t
2201 return t
2173
2202
2174 def _cleanup(self):
2203 def _cleanup(self):
2175 """Clean up state from this test invocation."""
2204 """Clean up state from this test invocation."""
2176 if self._chgsockdir:
2205 if self._chgsockdir:
2177 self._killchgdaemons()
2206 self._killchgdaemons()
2178
2207
2179 if self.options.keep_tmpdir:
2208 if self.options.keep_tmpdir:
2180 return
2209 return
2181
2210
2182 vlog("# Cleaning up HGTMP", self._hgtmp)
2211 vlog("# Cleaning up HGTMP", self._hgtmp)
2183 shutil.rmtree(self._hgtmp, True)
2212 shutil.rmtree(self._hgtmp, True)
2184 for f in self._createdfiles:
2213 for f in self._createdfiles:
2185 try:
2214 try:
2186 os.remove(f)
2215 os.remove(f)
2187 except OSError:
2216 except OSError:
2188 pass
2217 pass
2189
2218
2190 def _usecorrectpython(self):
2219 def _usecorrectpython(self):
2191 """Configure the environment to use the appropriate Python in tests."""
2220 """Configure the environment to use the appropriate Python in tests."""
2192 # Tests must use the same interpreter as us or bad things will happen.
2221 # Tests must use the same interpreter as us or bad things will happen.
2193 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2222 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2194 if getattr(os, 'symlink', None):
2223 if getattr(os, 'symlink', None):
2195 vlog("# Making python executable in test path a symlink to '%s'" %
2224 vlog("# Making python executable in test path a symlink to '%s'" %
2196 sys.executable)
2225 sys.executable)
2197 mypython = os.path.join(self._tmpbindir, pyexename)
2226 mypython = os.path.join(self._tmpbindir, pyexename)
2198 try:
2227 try:
2199 if os.readlink(mypython) == sys.executable:
2228 if os.readlink(mypython) == sys.executable:
2200 return
2229 return
2201 os.unlink(mypython)
2230 os.unlink(mypython)
2202 except OSError as err:
2231 except OSError as err:
2203 if err.errno != errno.ENOENT:
2232 if err.errno != errno.ENOENT:
2204 raise
2233 raise
2205 if self._findprogram(pyexename) != sys.executable:
2234 if self._findprogram(pyexename) != sys.executable:
2206 try:
2235 try:
2207 os.symlink(sys.executable, mypython)
2236 os.symlink(sys.executable, mypython)
2208 self._createdfiles.append(mypython)
2237 self._createdfiles.append(mypython)
2209 except OSError as err:
2238 except OSError as err:
2210 # child processes may race, which is harmless
2239 # child processes may race, which is harmless
2211 if err.errno != errno.EEXIST:
2240 if err.errno != errno.EEXIST:
2212 raise
2241 raise
2213 else:
2242 else:
2214 exedir, exename = os.path.split(sys.executable)
2243 exedir, exename = os.path.split(sys.executable)
2215 vlog("# Modifying search path to find %s as %s in '%s'" %
2244 vlog("# Modifying search path to find %s as %s in '%s'" %
2216 (exename, pyexename, exedir))
2245 (exename, pyexename, exedir))
2217 path = os.environ['PATH'].split(os.pathsep)
2246 path = os.environ['PATH'].split(os.pathsep)
2218 while exedir in path:
2247 while exedir in path:
2219 path.remove(exedir)
2248 path.remove(exedir)
2220 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2249 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2221 if not self._findprogram(pyexename):
2250 if not self._findprogram(pyexename):
2222 print("WARNING: Cannot find %s in search path" % pyexename)
2251 print("WARNING: Cannot find %s in search path" % pyexename)
2223
2252
2224 def _installhg(self):
2253 def _installhg(self):
2225 """Install hg into the test environment.
2254 """Install hg into the test environment.
2226
2255
2227 This will also configure hg with the appropriate testing settings.
2256 This will also configure hg with the appropriate testing settings.
2228 """
2257 """
2229 vlog("# Performing temporary installation of HG")
2258 vlog("# Performing temporary installation of HG")
2230 installerrs = os.path.join(b"tests", b"install.err")
2259 installerrs = os.path.join(b"tests", b"install.err")
2231 compiler = ''
2260 compiler = ''
2232 if self.options.compiler:
2261 if self.options.compiler:
2233 compiler = '--compiler ' + self.options.compiler
2262 compiler = '--compiler ' + self.options.compiler
2234 if self.options.pure:
2263 if self.options.pure:
2235 pure = b"--pure"
2264 pure = b"--pure"
2236 else:
2265 else:
2237 pure = b""
2266 pure = b""
2238
2267
2239 # Run installer in hg root
2268 # Run installer in hg root
2240 script = os.path.realpath(sys.argv[0])
2269 script = os.path.realpath(sys.argv[0])
2241 exe = sys.executable
2270 exe = sys.executable
2242 if PYTHON3:
2271 if PYTHON3:
2243 compiler = _bytespath(compiler)
2272 compiler = _bytespath(compiler)
2244 script = _bytespath(script)
2273 script = _bytespath(script)
2245 exe = _bytespath(exe)
2274 exe = _bytespath(exe)
2246 hgroot = os.path.dirname(os.path.dirname(script))
2275 hgroot = os.path.dirname(os.path.dirname(script))
2247 self._hgroot = hgroot
2276 self._hgroot = hgroot
2248 os.chdir(hgroot)
2277 os.chdir(hgroot)
2249 nohome = b'--home=""'
2278 nohome = b'--home=""'
2250 if os.name == 'nt':
2279 if os.name == 'nt':
2251 # The --home="" trick works only on OS where os.sep == '/'
2280 # The --home="" trick works only on OS where os.sep == '/'
2252 # because of a distutils convert_path() fast-path. Avoid it at
2281 # because of a distutils convert_path() fast-path. Avoid it at
2253 # least on Windows for now, deal with .pydistutils.cfg bugs
2282 # least on Windows for now, deal with .pydistutils.cfg bugs
2254 # when they happen.
2283 # when they happen.
2255 nohome = b''
2284 nohome = b''
2256 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2285 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2257 b' build %(compiler)s --build-base="%(base)s"'
2286 b' build %(compiler)s --build-base="%(base)s"'
2258 b' install --force --prefix="%(prefix)s"'
2287 b' install --force --prefix="%(prefix)s"'
2259 b' --install-lib="%(libdir)s"'
2288 b' --install-lib="%(libdir)s"'
2260 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2289 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2261 % {b'exe': exe, b'pure': pure,
2290 % {b'exe': exe, b'pure': pure,
2262 b'compiler': compiler,
2291 b'compiler': compiler,
2263 b'base': os.path.join(self._hgtmp, b"build"),
2292 b'base': os.path.join(self._hgtmp, b"build"),
2264 b'prefix': self._installdir, b'libdir': self._pythondir,
2293 b'prefix': self._installdir, b'libdir': self._pythondir,
2265 b'bindir': self._bindir,
2294 b'bindir': self._bindir,
2266 b'nohome': nohome, b'logfile': installerrs})
2295 b'nohome': nohome, b'logfile': installerrs})
2267
2296
2268 # setuptools requires install directories to exist.
2297 # setuptools requires install directories to exist.
2269 def makedirs(p):
2298 def makedirs(p):
2270 try:
2299 try:
2271 os.makedirs(p)
2300 os.makedirs(p)
2272 except OSError as e:
2301 except OSError as e:
2273 if e.errno != errno.EEXIST:
2302 if e.errno != errno.EEXIST:
2274 raise
2303 raise
2275 makedirs(self._pythondir)
2304 makedirs(self._pythondir)
2276 makedirs(self._bindir)
2305 makedirs(self._bindir)
2277
2306
2278 vlog("# Running", cmd)
2307 vlog("# Running", cmd)
2279 if os.system(cmd) == 0:
2308 if os.system(cmd) == 0:
2280 if not self.options.verbose:
2309 if not self.options.verbose:
2281 try:
2310 try:
2282 os.remove(installerrs)
2311 os.remove(installerrs)
2283 except OSError as e:
2312 except OSError as e:
2284 if e.errno != errno.ENOENT:
2313 if e.errno != errno.ENOENT:
2285 raise
2314 raise
2286 else:
2315 else:
2287 f = open(installerrs, 'rb')
2316 f = open(installerrs, 'rb')
2288 for line in f:
2317 for line in f:
2289 if PYTHON3:
2318 if PYTHON3:
2290 sys.stdout.buffer.write(line)
2319 sys.stdout.buffer.write(line)
2291 else:
2320 else:
2292 sys.stdout.write(line)
2321 sys.stdout.write(line)
2293 f.close()
2322 f.close()
2294 sys.exit(1)
2323 sys.exit(1)
2295 os.chdir(self._testdir)
2324 os.chdir(self._testdir)
2296
2325
2297 self._usecorrectpython()
2326 self._usecorrectpython()
2298
2327
2299 if self.options.py3k_warnings and not self.options.anycoverage:
2328 if self.options.py3k_warnings and not self.options.anycoverage:
2300 vlog("# Updating hg command to enable Py3k Warnings switch")
2329 vlog("# Updating hg command to enable Py3k Warnings switch")
2301 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2330 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2302 lines = [line.rstrip() for line in f]
2331 lines = [line.rstrip() for line in f]
2303 lines[0] += ' -3'
2332 lines[0] += ' -3'
2304 f.close()
2333 f.close()
2305 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2334 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2306 for line in lines:
2335 for line in lines:
2307 f.write(line + '\n')
2336 f.write(line + '\n')
2308 f.close()
2337 f.close()
2309
2338
2310 hgbat = os.path.join(self._bindir, b'hg.bat')
2339 hgbat = os.path.join(self._bindir, b'hg.bat')
2311 if os.path.isfile(hgbat):
2340 if os.path.isfile(hgbat):
2312 # hg.bat expects to be put in bin/scripts while run-tests.py
2341 # hg.bat expects to be put in bin/scripts while run-tests.py
2313 # installation layout put it in bin/ directly. Fix it
2342 # installation layout put it in bin/ directly. Fix it
2314 f = open(hgbat, 'rb')
2343 f = open(hgbat, 'rb')
2315 data = f.read()
2344 data = f.read()
2316 f.close()
2345 f.close()
2317 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2346 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2318 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2347 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2319 b'"%~dp0python" "%~dp0hg" %*')
2348 b'"%~dp0python" "%~dp0hg" %*')
2320 f = open(hgbat, 'wb')
2349 f = open(hgbat, 'wb')
2321 f.write(data)
2350 f.write(data)
2322 f.close()
2351 f.close()
2323 else:
2352 else:
2324 print('WARNING: cannot fix hg.bat reference to python.exe')
2353 print('WARNING: cannot fix hg.bat reference to python.exe')
2325
2354
2326 if self.options.anycoverage:
2355 if self.options.anycoverage:
2327 custom = os.path.join(self._testdir, 'sitecustomize.py')
2356 custom = os.path.join(self._testdir, 'sitecustomize.py')
2328 target = os.path.join(self._pythondir, 'sitecustomize.py')
2357 target = os.path.join(self._pythondir, 'sitecustomize.py')
2329 vlog('# Installing coverage trigger to %s' % target)
2358 vlog('# Installing coverage trigger to %s' % target)
2330 shutil.copyfile(custom, target)
2359 shutil.copyfile(custom, target)
2331 rc = os.path.join(self._testdir, '.coveragerc')
2360 rc = os.path.join(self._testdir, '.coveragerc')
2332 vlog('# Installing coverage rc to %s' % rc)
2361 vlog('# Installing coverage rc to %s' % rc)
2333 os.environ['COVERAGE_PROCESS_START'] = rc
2362 os.environ['COVERAGE_PROCESS_START'] = rc
2334 covdir = os.path.join(self._installdir, '..', 'coverage')
2363 covdir = os.path.join(self._installdir, '..', 'coverage')
2335 try:
2364 try:
2336 os.mkdir(covdir)
2365 os.mkdir(covdir)
2337 except OSError as e:
2366 except OSError as e:
2338 if e.errno != errno.EEXIST:
2367 if e.errno != errno.EEXIST:
2339 raise
2368 raise
2340
2369
2341 os.environ['COVERAGE_DIR'] = covdir
2370 os.environ['COVERAGE_DIR'] = covdir
2342
2371
2343 def _checkhglib(self, verb):
2372 def _checkhglib(self, verb):
2344 """Ensure that the 'mercurial' package imported by python is
2373 """Ensure that the 'mercurial' package imported by python is
2345 the one we expect it to be. If not, print a warning to stderr."""
2374 the one we expect it to be. If not, print a warning to stderr."""
2346 if ((self._bindir == self._pythondir) and
2375 if ((self._bindir == self._pythondir) and
2347 (self._bindir != self._tmpbindir)):
2376 (self._bindir != self._tmpbindir)):
2348 # The pythondir has been inferred from --with-hg flag.
2377 # The pythondir has been inferred from --with-hg flag.
2349 # We cannot expect anything sensible here.
2378 # We cannot expect anything sensible here.
2350 return
2379 return
2351 expecthg = os.path.join(self._pythondir, b'mercurial')
2380 expecthg = os.path.join(self._pythondir, b'mercurial')
2352 actualhg = self._gethgpath()
2381 actualhg = self._gethgpath()
2353 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2382 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2354 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2383 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2355 ' (expected %s)\n'
2384 ' (expected %s)\n'
2356 % (verb, actualhg, expecthg))
2385 % (verb, actualhg, expecthg))
2357 def _gethgpath(self):
2386 def _gethgpath(self):
2358 """Return the path to the mercurial package that is actually found by
2387 """Return the path to the mercurial package that is actually found by
2359 the current Python interpreter."""
2388 the current Python interpreter."""
2360 if self._hgpath is not None:
2389 if self._hgpath is not None:
2361 return self._hgpath
2390 return self._hgpath
2362
2391
2363 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2392 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2364 cmd = cmd % PYTHON
2393 cmd = cmd % PYTHON
2365 if PYTHON3:
2394 if PYTHON3:
2366 cmd = _strpath(cmd)
2395 cmd = _strpath(cmd)
2367 pipe = os.popen(cmd)
2396 pipe = os.popen(cmd)
2368 try:
2397 try:
2369 self._hgpath = _bytespath(pipe.read().strip())
2398 self._hgpath = _bytespath(pipe.read().strip())
2370 finally:
2399 finally:
2371 pipe.close()
2400 pipe.close()
2372
2401
2373 return self._hgpath
2402 return self._hgpath
2374
2403
2375 def _installchg(self):
2404 def _installchg(self):
2376 """Install chg into the test environment"""
2405 """Install chg into the test environment"""
2377 vlog('# Performing temporary installation of CHG')
2406 vlog('# Performing temporary installation of CHG')
2378 assert os.path.dirname(self._bindir) == self._installdir
2407 assert os.path.dirname(self._bindir) == self._installdir
2379 assert self._hgroot, 'must be called after _installhg()'
2408 assert self._hgroot, 'must be called after _installhg()'
2380 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2409 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2381 % {b'make': 'make', # TODO: switch by option or environment?
2410 % {b'make': 'make', # TODO: switch by option or environment?
2382 b'prefix': self._installdir})
2411 b'prefix': self._installdir})
2383 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2412 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2384 vlog("# Running", cmd)
2413 vlog("# Running", cmd)
2385 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2414 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2386 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2415 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2387 stderr=subprocess.STDOUT)
2416 stderr=subprocess.STDOUT)
2388 out, _err = proc.communicate()
2417 out, _err = proc.communicate()
2389 if proc.returncode != 0:
2418 if proc.returncode != 0:
2390 if PYTHON3:
2419 if PYTHON3:
2391 sys.stdout.buffer.write(out)
2420 sys.stdout.buffer.write(out)
2392 else:
2421 else:
2393 sys.stdout.write(out)
2422 sys.stdout.write(out)
2394 sys.exit(1)
2423 sys.exit(1)
2395
2424
2396 def _killchgdaemons(self):
2425 def _killchgdaemons(self):
2397 """Kill all background chg command servers spawned by tests"""
2426 """Kill all background chg command servers spawned by tests"""
2398 for f in os.listdir(self._chgsockdir):
2427 for f in os.listdir(self._chgsockdir):
2399 if '.' in f:
2428 if '.' in f:
2400 continue
2429 continue
2401 os.unlink(os.path.join(self._chgsockdir, f))
2430 os.unlink(os.path.join(self._chgsockdir, f))
2402
2431
2403 def _outputcoverage(self):
2432 def _outputcoverage(self):
2404 """Produce code coverage output."""
2433 """Produce code coverage output."""
2405 from coverage import coverage
2434 from coverage import coverage
2406
2435
2407 vlog('# Producing coverage report')
2436 vlog('# Producing coverage report')
2408 # chdir is the easiest way to get short, relative paths in the
2437 # chdir is the easiest way to get short, relative paths in the
2409 # output.
2438 # output.
2410 os.chdir(self._hgroot)
2439 os.chdir(self._hgroot)
2411 covdir = os.path.join(self._installdir, '..', 'coverage')
2440 covdir = os.path.join(self._installdir, '..', 'coverage')
2412 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2441 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2413
2442
2414 # Map install directory paths back to source directory.
2443 # Map install directory paths back to source directory.
2415 cov.config.paths['srcdir'] = ['.', self._pythondir]
2444 cov.config.paths['srcdir'] = ['.', self._pythondir]
2416
2445
2417 cov.combine()
2446 cov.combine()
2418
2447
2419 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2448 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2420 cov.report(ignore_errors=True, omit=omit)
2449 cov.report(ignore_errors=True, omit=omit)
2421
2450
2422 if self.options.htmlcov:
2451 if self.options.htmlcov:
2423 htmldir = os.path.join(self._testdir, 'htmlcov')
2452 htmldir = os.path.join(self._testdir, 'htmlcov')
2424 cov.html_report(directory=htmldir, omit=omit)
2453 cov.html_report(directory=htmldir, omit=omit)
2425 if self.options.annotate:
2454 if self.options.annotate:
2426 adir = os.path.join(self._testdir, 'annotated')
2455 adir = os.path.join(self._testdir, 'annotated')
2427 if not os.path.isdir(adir):
2456 if not os.path.isdir(adir):
2428 os.mkdir(adir)
2457 os.mkdir(adir)
2429 cov.annotate(directory=adir, omit=omit)
2458 cov.annotate(directory=adir, omit=omit)
2430
2459
2431 def _findprogram(self, program):
2460 def _findprogram(self, program):
2432 """Search PATH for a executable program"""
2461 """Search PATH for a executable program"""
2433 dpb = _bytespath(os.defpath)
2462 dpb = _bytespath(os.defpath)
2434 sepb = _bytespath(os.pathsep)
2463 sepb = _bytespath(os.pathsep)
2435 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2464 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2436 name = os.path.join(p, program)
2465 name = os.path.join(p, program)
2437 if os.name == 'nt' or os.access(name, os.X_OK):
2466 if os.name == 'nt' or os.access(name, os.X_OK):
2438 return name
2467 return name
2439 return None
2468 return None
2440
2469
2441 def _checktools(self):
2470 def _checktools(self):
2442 """Ensure tools required to run tests are present."""
2471 """Ensure tools required to run tests are present."""
2443 for p in self.REQUIREDTOOLS:
2472 for p in self.REQUIREDTOOLS:
2444 if os.name == 'nt' and not p.endswith('.exe'):
2473 if os.name == 'nt' and not p.endswith('.exe'):
2445 p += '.exe'
2474 p += '.exe'
2446 found = self._findprogram(p)
2475 found = self._findprogram(p)
2447 if found:
2476 if found:
2448 vlog("# Found prerequisite", p, "at", found)
2477 vlog("# Found prerequisite", p, "at", found)
2449 else:
2478 else:
2450 print("WARNING: Did not find prerequisite tool: %s " % p)
2479 print("WARNING: Did not find prerequisite tool: %s " % p)
2451
2480
2452 if __name__ == '__main__':
2481 if __name__ == '__main__':
2453 runner = TestRunner()
2482 runner = TestRunner()
2454
2483
2455 try:
2484 try:
2456 import msvcrt
2485 import msvcrt
2457 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2486 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2458 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2487 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2459 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2488 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2460 except ImportError:
2489 except ImportError:
2461 pass
2490 pass
2462
2491
2463 sys.exit(runner.run(sys.argv[1:]))
2492 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now