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