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