##// END OF EJS Templates
ui: separate option to show prompt echo, enabled only in tests (issue4417)...
Yuya Nishihara -
r23053:5ba11ab4 stable
parent child Browse files
Show More
@@ -1,923 +1,919 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, socket, sys, tempfile, traceback
9 import errno, getpass, os, socket, sys, tempfile, traceback
10 import config, scmutil, util, error, formatter
10 import config, scmutil, util, error, formatter
11 from node import hex
11 from node import hex
12
12
13 samplehgrcs = {
13 samplehgrcs = {
14 'user':
14 'user':
15 """# example user config (see "hg help config" for more info)
15 """# example user config (see "hg help config" for more info)
16 [ui]
16 [ui]
17 # name and email, e.g.
17 # name and email, e.g.
18 # username = Jane Doe <jdoe@example.com>
18 # username = Jane Doe <jdoe@example.com>
19 username =
19 username =
20
20
21 [extensions]
21 [extensions]
22 # uncomment these lines to enable some popular extensions
22 # uncomment these lines to enable some popular extensions
23 # (see "hg help extensions" for more info)
23 # (see "hg help extensions" for more info)
24 #
24 #
25 # pager =
25 # pager =
26 # progress =
26 # progress =
27 # color =""",
27 # color =""",
28
28
29 'cloned':
29 'cloned':
30 """# example repository config (see "hg help config" for more info)
30 """# example repository config (see "hg help config" for more info)
31 [paths]
31 [paths]
32 default = %s
32 default = %s
33
33
34 # path aliases to other clones of this repo in URLs or filesystem paths
34 # path aliases to other clones of this repo in URLs or filesystem paths
35 # (see "hg help config.paths" for more info)
35 # (see "hg help config.paths" for more info)
36 #
36 #
37 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
37 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
38 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
38 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
39 # my-clone = /home/jdoe/jdoes-clone
39 # my-clone = /home/jdoe/jdoes-clone
40
40
41 [ui]
41 [ui]
42 # name and email (local to this repository, optional), e.g.
42 # name and email (local to this repository, optional), e.g.
43 # username = Jane Doe <jdoe@example.com>
43 # username = Jane Doe <jdoe@example.com>
44 """,
44 """,
45
45
46 'local':
46 'local':
47 """# example repository config (see "hg help config" for more info)
47 """# example repository config (see "hg help config" for more info)
48 [paths]
48 [paths]
49 # path aliases to other clones of this repo in URLs or filesystem paths
49 # path aliases to other clones of this repo in URLs or filesystem paths
50 # (see "hg help config.paths" for more info)
50 # (see "hg help config.paths" for more info)
51 #
51 #
52 # default = http://example.com/hg/example-repo
52 # default = http://example.com/hg/example-repo
53 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
53 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
54 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
54 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
55 # my-clone = /home/jdoe/jdoes-clone
55 # my-clone = /home/jdoe/jdoes-clone
56
56
57 [ui]
57 [ui]
58 # name and email (local to this repository, optional), e.g.
58 # name and email (local to this repository, optional), e.g.
59 # username = Jane Doe <jdoe@example.com>
59 # username = Jane Doe <jdoe@example.com>
60 """,
60 """,
61
61
62 'global':
62 'global':
63 """# example system-wide hg config (see "hg help config" for more info)
63 """# example system-wide hg config (see "hg help config" for more info)
64
64
65 [extensions]
65 [extensions]
66 # uncomment these lines to enable some popular extensions
66 # uncomment these lines to enable some popular extensions
67 # (see "hg help extensions" for more info)
67 # (see "hg help extensions" for more info)
68 #
68 #
69 # blackbox =
69 # blackbox =
70 # progress =
70 # progress =
71 # color =
71 # color =
72 # pager =""",
72 # pager =""",
73 }
73 }
74
74
75 class ui(object):
75 class ui(object):
76 def __init__(self, src=None):
76 def __init__(self, src=None):
77 # _buffers: used for temporary capture of output
77 # _buffers: used for temporary capture of output
78 self._buffers = []
78 self._buffers = []
79 # _bufferstates: Should the temporary capture includes stderr
79 # _bufferstates: Should the temporary capture includes stderr
80 self._bufferstates = []
80 self._bufferstates = []
81 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
81 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
82 self._reportuntrusted = True
82 self._reportuntrusted = True
83 self._ocfg = config.config() # overlay
83 self._ocfg = config.config() # overlay
84 self._tcfg = config.config() # trusted
84 self._tcfg = config.config() # trusted
85 self._ucfg = config.config() # untrusted
85 self._ucfg = config.config() # untrusted
86 self._trustusers = set()
86 self._trustusers = set()
87 self._trustgroups = set()
87 self._trustgroups = set()
88 self.callhooks = True
88 self.callhooks = True
89
89
90 if src:
90 if src:
91 self.fout = src.fout
91 self.fout = src.fout
92 self.ferr = src.ferr
92 self.ferr = src.ferr
93 self.fin = src.fin
93 self.fin = src.fin
94
94
95 self._tcfg = src._tcfg.copy()
95 self._tcfg = src._tcfg.copy()
96 self._ucfg = src._ucfg.copy()
96 self._ucfg = src._ucfg.copy()
97 self._ocfg = src._ocfg.copy()
97 self._ocfg = src._ocfg.copy()
98 self._trustusers = src._trustusers.copy()
98 self._trustusers = src._trustusers.copy()
99 self._trustgroups = src._trustgroups.copy()
99 self._trustgroups = src._trustgroups.copy()
100 self.environ = src.environ
100 self.environ = src.environ
101 self.callhooks = src.callhooks
101 self.callhooks = src.callhooks
102 self.fixconfig()
102 self.fixconfig()
103 else:
103 else:
104 self.fout = sys.stdout
104 self.fout = sys.stdout
105 self.ferr = sys.stderr
105 self.ferr = sys.stderr
106 self.fin = sys.stdin
106 self.fin = sys.stdin
107
107
108 # shared read-only environment
108 # shared read-only environment
109 self.environ = os.environ
109 self.environ = os.environ
110 # we always trust global config files
110 # we always trust global config files
111 for f in scmutil.rcpath():
111 for f in scmutil.rcpath():
112 self.readconfig(f, trust=True)
112 self.readconfig(f, trust=True)
113
113
114 def copy(self):
114 def copy(self):
115 return self.__class__(self)
115 return self.__class__(self)
116
116
117 def formatter(self, topic, opts):
117 def formatter(self, topic, opts):
118 return formatter.formatter(self, topic, opts)
118 return formatter.formatter(self, topic, opts)
119
119
120 def _trusted(self, fp, f):
120 def _trusted(self, fp, f):
121 st = util.fstat(fp)
121 st = util.fstat(fp)
122 if util.isowner(st):
122 if util.isowner(st):
123 return True
123 return True
124
124
125 tusers, tgroups = self._trustusers, self._trustgroups
125 tusers, tgroups = self._trustusers, self._trustgroups
126 if '*' in tusers or '*' in tgroups:
126 if '*' in tusers or '*' in tgroups:
127 return True
127 return True
128
128
129 user = util.username(st.st_uid)
129 user = util.username(st.st_uid)
130 group = util.groupname(st.st_gid)
130 group = util.groupname(st.st_gid)
131 if user in tusers or group in tgroups or user == util.username():
131 if user in tusers or group in tgroups or user == util.username():
132 return True
132 return True
133
133
134 if self._reportuntrusted:
134 if self._reportuntrusted:
135 self.warn(_('not trusting file %s from untrusted '
135 self.warn(_('not trusting file %s from untrusted '
136 'user %s, group %s\n') % (f, user, group))
136 'user %s, group %s\n') % (f, user, group))
137 return False
137 return False
138
138
139 def readconfig(self, filename, root=None, trust=False,
139 def readconfig(self, filename, root=None, trust=False,
140 sections=None, remap=None):
140 sections=None, remap=None):
141 try:
141 try:
142 fp = open(filename)
142 fp = open(filename)
143 except IOError:
143 except IOError:
144 if not sections: # ignore unless we were looking for something
144 if not sections: # ignore unless we were looking for something
145 return
145 return
146 raise
146 raise
147
147
148 cfg = config.config()
148 cfg = config.config()
149 trusted = sections or trust or self._trusted(fp, filename)
149 trusted = sections or trust or self._trusted(fp, filename)
150
150
151 try:
151 try:
152 cfg.read(filename, fp, sections=sections, remap=remap)
152 cfg.read(filename, fp, sections=sections, remap=remap)
153 fp.close()
153 fp.close()
154 except error.ConfigError, inst:
154 except error.ConfigError, inst:
155 if trusted:
155 if trusted:
156 raise
156 raise
157 self.warn(_("ignored: %s\n") % str(inst))
157 self.warn(_("ignored: %s\n") % str(inst))
158
158
159 if self.plain():
159 if self.plain():
160 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
160 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
161 'logtemplate', 'style',
161 'logtemplate', 'style',
162 'traceback', 'verbose'):
162 'traceback', 'verbose'):
163 if k in cfg['ui']:
163 if k in cfg['ui']:
164 del cfg['ui'][k]
164 del cfg['ui'][k]
165 for k, v in cfg.items('defaults'):
165 for k, v in cfg.items('defaults'):
166 del cfg['defaults'][k]
166 del cfg['defaults'][k]
167 # Don't remove aliases from the configuration if in the exceptionlist
167 # Don't remove aliases from the configuration if in the exceptionlist
168 if self.plain('alias'):
168 if self.plain('alias'):
169 for k, v in cfg.items('alias'):
169 for k, v in cfg.items('alias'):
170 del cfg['alias'][k]
170 del cfg['alias'][k]
171
171
172 if trusted:
172 if trusted:
173 self._tcfg.update(cfg)
173 self._tcfg.update(cfg)
174 self._tcfg.update(self._ocfg)
174 self._tcfg.update(self._ocfg)
175 self._ucfg.update(cfg)
175 self._ucfg.update(cfg)
176 self._ucfg.update(self._ocfg)
176 self._ucfg.update(self._ocfg)
177
177
178 if root is None:
178 if root is None:
179 root = os.path.expanduser('~')
179 root = os.path.expanduser('~')
180 self.fixconfig(root=root)
180 self.fixconfig(root=root)
181
181
182 def fixconfig(self, root=None, section=None):
182 def fixconfig(self, root=None, section=None):
183 if section in (None, 'paths'):
183 if section in (None, 'paths'):
184 # expand vars and ~
184 # expand vars and ~
185 # translate paths relative to root (or home) into absolute paths
185 # translate paths relative to root (or home) into absolute paths
186 root = root or os.getcwd()
186 root = root or os.getcwd()
187 for c in self._tcfg, self._ucfg, self._ocfg:
187 for c in self._tcfg, self._ucfg, self._ocfg:
188 for n, p in c.items('paths'):
188 for n, p in c.items('paths'):
189 if not p:
189 if not p:
190 continue
190 continue
191 if '%%' in p:
191 if '%%' in p:
192 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
192 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
193 % (n, p, self.configsource('paths', n)))
193 % (n, p, self.configsource('paths', n)))
194 p = p.replace('%%', '%')
194 p = p.replace('%%', '%')
195 p = util.expandpath(p)
195 p = util.expandpath(p)
196 if not util.hasscheme(p) and not os.path.isabs(p):
196 if not util.hasscheme(p) and not os.path.isabs(p):
197 p = os.path.normpath(os.path.join(root, p))
197 p = os.path.normpath(os.path.join(root, p))
198 c.set("paths", n, p)
198 c.set("paths", n, p)
199
199
200 if section in (None, 'ui'):
200 if section in (None, 'ui'):
201 # update ui options
201 # update ui options
202 self.debugflag = self.configbool('ui', 'debug')
202 self.debugflag = self.configbool('ui', 'debug')
203 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
203 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
204 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
204 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
205 if self.verbose and self.quiet:
205 if self.verbose and self.quiet:
206 self.quiet = self.verbose = False
206 self.quiet = self.verbose = False
207 self._reportuntrusted = self.debugflag or self.configbool("ui",
207 self._reportuntrusted = self.debugflag or self.configbool("ui",
208 "report_untrusted", True)
208 "report_untrusted", True)
209 self.tracebackflag = self.configbool('ui', 'traceback', False)
209 self.tracebackflag = self.configbool('ui', 'traceback', False)
210
210
211 if section in (None, 'trusted'):
211 if section in (None, 'trusted'):
212 # update trust information
212 # update trust information
213 self._trustusers.update(self.configlist('trusted', 'users'))
213 self._trustusers.update(self.configlist('trusted', 'users'))
214 self._trustgroups.update(self.configlist('trusted', 'groups'))
214 self._trustgroups.update(self.configlist('trusted', 'groups'))
215
215
216 def backupconfig(self, section, item):
216 def backupconfig(self, section, item):
217 return (self._ocfg.backup(section, item),
217 return (self._ocfg.backup(section, item),
218 self._tcfg.backup(section, item),
218 self._tcfg.backup(section, item),
219 self._ucfg.backup(section, item),)
219 self._ucfg.backup(section, item),)
220 def restoreconfig(self, data):
220 def restoreconfig(self, data):
221 self._ocfg.restore(data[0])
221 self._ocfg.restore(data[0])
222 self._tcfg.restore(data[1])
222 self._tcfg.restore(data[1])
223 self._ucfg.restore(data[2])
223 self._ucfg.restore(data[2])
224
224
225 def setconfig(self, section, name, value, source=''):
225 def setconfig(self, section, name, value, source=''):
226 for cfg in (self._ocfg, self._tcfg, self._ucfg):
226 for cfg in (self._ocfg, self._tcfg, self._ucfg):
227 cfg.set(section, name, value, source)
227 cfg.set(section, name, value, source)
228 self.fixconfig(section=section)
228 self.fixconfig(section=section)
229
229
230 def _data(self, untrusted):
230 def _data(self, untrusted):
231 return untrusted and self._ucfg or self._tcfg
231 return untrusted and self._ucfg or self._tcfg
232
232
233 def configsource(self, section, name, untrusted=False):
233 def configsource(self, section, name, untrusted=False):
234 return self._data(untrusted).source(section, name) or 'none'
234 return self._data(untrusted).source(section, name) or 'none'
235
235
236 def config(self, section, name, default=None, untrusted=False):
236 def config(self, section, name, default=None, untrusted=False):
237 if isinstance(name, list):
237 if isinstance(name, list):
238 alternates = name
238 alternates = name
239 else:
239 else:
240 alternates = [name]
240 alternates = [name]
241
241
242 for n in alternates:
242 for n in alternates:
243 value = self._data(untrusted).get(section, n, None)
243 value = self._data(untrusted).get(section, n, None)
244 if value is not None:
244 if value is not None:
245 name = n
245 name = n
246 break
246 break
247 else:
247 else:
248 value = default
248 value = default
249
249
250 if self.debugflag and not untrusted and self._reportuntrusted:
250 if self.debugflag and not untrusted and self._reportuntrusted:
251 for n in alternates:
251 for n in alternates:
252 uvalue = self._ucfg.get(section, n)
252 uvalue = self._ucfg.get(section, n)
253 if uvalue is not None and uvalue != value:
253 if uvalue is not None and uvalue != value:
254 self.debug("ignoring untrusted configuration option "
254 self.debug("ignoring untrusted configuration option "
255 "%s.%s = %s\n" % (section, n, uvalue))
255 "%s.%s = %s\n" % (section, n, uvalue))
256 return value
256 return value
257
257
258 def configpath(self, section, name, default=None, untrusted=False):
258 def configpath(self, section, name, default=None, untrusted=False):
259 'get a path config item, expanded relative to repo root or config file'
259 'get a path config item, expanded relative to repo root or config file'
260 v = self.config(section, name, default, untrusted)
260 v = self.config(section, name, default, untrusted)
261 if v is None:
261 if v is None:
262 return None
262 return None
263 if not os.path.isabs(v) or "://" not in v:
263 if not os.path.isabs(v) or "://" not in v:
264 src = self.configsource(section, name, untrusted)
264 src = self.configsource(section, name, untrusted)
265 if ':' in src:
265 if ':' in src:
266 base = os.path.dirname(src.rsplit(':')[0])
266 base = os.path.dirname(src.rsplit(':')[0])
267 v = os.path.join(base, os.path.expanduser(v))
267 v = os.path.join(base, os.path.expanduser(v))
268 return v
268 return v
269
269
270 def configbool(self, section, name, default=False, untrusted=False):
270 def configbool(self, section, name, default=False, untrusted=False):
271 """parse a configuration element as a boolean
271 """parse a configuration element as a boolean
272
272
273 >>> u = ui(); s = 'foo'
273 >>> u = ui(); s = 'foo'
274 >>> u.setconfig(s, 'true', 'yes')
274 >>> u.setconfig(s, 'true', 'yes')
275 >>> u.configbool(s, 'true')
275 >>> u.configbool(s, 'true')
276 True
276 True
277 >>> u.setconfig(s, 'false', 'no')
277 >>> u.setconfig(s, 'false', 'no')
278 >>> u.configbool(s, 'false')
278 >>> u.configbool(s, 'false')
279 False
279 False
280 >>> u.configbool(s, 'unknown')
280 >>> u.configbool(s, 'unknown')
281 False
281 False
282 >>> u.configbool(s, 'unknown', True)
282 >>> u.configbool(s, 'unknown', True)
283 True
283 True
284 >>> u.setconfig(s, 'invalid', 'somevalue')
284 >>> u.setconfig(s, 'invalid', 'somevalue')
285 >>> u.configbool(s, 'invalid')
285 >>> u.configbool(s, 'invalid')
286 Traceback (most recent call last):
286 Traceback (most recent call last):
287 ...
287 ...
288 ConfigError: foo.invalid is not a boolean ('somevalue')
288 ConfigError: foo.invalid is not a boolean ('somevalue')
289 """
289 """
290
290
291 v = self.config(section, name, None, untrusted)
291 v = self.config(section, name, None, untrusted)
292 if v is None:
292 if v is None:
293 return default
293 return default
294 if isinstance(v, bool):
294 if isinstance(v, bool):
295 return v
295 return v
296 b = util.parsebool(v)
296 b = util.parsebool(v)
297 if b is None:
297 if b is None:
298 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
298 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
299 % (section, name, v))
299 % (section, name, v))
300 return b
300 return b
301
301
302 def configint(self, section, name, default=None, untrusted=False):
302 def configint(self, section, name, default=None, untrusted=False):
303 """parse a configuration element as an integer
303 """parse a configuration element as an integer
304
304
305 >>> u = ui(); s = 'foo'
305 >>> u = ui(); s = 'foo'
306 >>> u.setconfig(s, 'int1', '42')
306 >>> u.setconfig(s, 'int1', '42')
307 >>> u.configint(s, 'int1')
307 >>> u.configint(s, 'int1')
308 42
308 42
309 >>> u.setconfig(s, 'int2', '-42')
309 >>> u.setconfig(s, 'int2', '-42')
310 >>> u.configint(s, 'int2')
310 >>> u.configint(s, 'int2')
311 -42
311 -42
312 >>> u.configint(s, 'unknown', 7)
312 >>> u.configint(s, 'unknown', 7)
313 7
313 7
314 >>> u.setconfig(s, 'invalid', 'somevalue')
314 >>> u.setconfig(s, 'invalid', 'somevalue')
315 >>> u.configint(s, 'invalid')
315 >>> u.configint(s, 'invalid')
316 Traceback (most recent call last):
316 Traceback (most recent call last):
317 ...
317 ...
318 ConfigError: foo.invalid is not an integer ('somevalue')
318 ConfigError: foo.invalid is not an integer ('somevalue')
319 """
319 """
320
320
321 v = self.config(section, name, None, untrusted)
321 v = self.config(section, name, None, untrusted)
322 if v is None:
322 if v is None:
323 return default
323 return default
324 try:
324 try:
325 return int(v)
325 return int(v)
326 except ValueError:
326 except ValueError:
327 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
327 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
328 % (section, name, v))
328 % (section, name, v))
329
329
330 def configbytes(self, section, name, default=0, untrusted=False):
330 def configbytes(self, section, name, default=0, untrusted=False):
331 """parse a configuration element as a quantity in bytes
331 """parse a configuration element as a quantity in bytes
332
332
333 Units can be specified as b (bytes), k or kb (kilobytes), m or
333 Units can be specified as b (bytes), k or kb (kilobytes), m or
334 mb (megabytes), g or gb (gigabytes).
334 mb (megabytes), g or gb (gigabytes).
335
335
336 >>> u = ui(); s = 'foo'
336 >>> u = ui(); s = 'foo'
337 >>> u.setconfig(s, 'val1', '42')
337 >>> u.setconfig(s, 'val1', '42')
338 >>> u.configbytes(s, 'val1')
338 >>> u.configbytes(s, 'val1')
339 42
339 42
340 >>> u.setconfig(s, 'val2', '42.5 kb')
340 >>> u.setconfig(s, 'val2', '42.5 kb')
341 >>> u.configbytes(s, 'val2')
341 >>> u.configbytes(s, 'val2')
342 43520
342 43520
343 >>> u.configbytes(s, 'unknown', '7 MB')
343 >>> u.configbytes(s, 'unknown', '7 MB')
344 7340032
344 7340032
345 >>> u.setconfig(s, 'invalid', 'somevalue')
345 >>> u.setconfig(s, 'invalid', 'somevalue')
346 >>> u.configbytes(s, 'invalid')
346 >>> u.configbytes(s, 'invalid')
347 Traceback (most recent call last):
347 Traceback (most recent call last):
348 ...
348 ...
349 ConfigError: foo.invalid is not a byte quantity ('somevalue')
349 ConfigError: foo.invalid is not a byte quantity ('somevalue')
350 """
350 """
351
351
352 value = self.config(section, name)
352 value = self.config(section, name)
353 if value is None:
353 if value is None:
354 if not isinstance(default, str):
354 if not isinstance(default, str):
355 return default
355 return default
356 value = default
356 value = default
357 try:
357 try:
358 return util.sizetoint(value)
358 return util.sizetoint(value)
359 except error.ParseError:
359 except error.ParseError:
360 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
360 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
361 % (section, name, value))
361 % (section, name, value))
362
362
363 def configlist(self, section, name, default=None, untrusted=False):
363 def configlist(self, section, name, default=None, untrusted=False):
364 """parse a configuration element as a list of comma/space separated
364 """parse a configuration element as a list of comma/space separated
365 strings
365 strings
366
366
367 >>> u = ui(); s = 'foo'
367 >>> u = ui(); s = 'foo'
368 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
368 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
369 >>> u.configlist(s, 'list1')
369 >>> u.configlist(s, 'list1')
370 ['this', 'is', 'a small', 'test']
370 ['this', 'is', 'a small', 'test']
371 """
371 """
372
372
373 def _parse_plain(parts, s, offset):
373 def _parse_plain(parts, s, offset):
374 whitespace = False
374 whitespace = False
375 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
375 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
376 whitespace = True
376 whitespace = True
377 offset += 1
377 offset += 1
378 if offset >= len(s):
378 if offset >= len(s):
379 return None, parts, offset
379 return None, parts, offset
380 if whitespace:
380 if whitespace:
381 parts.append('')
381 parts.append('')
382 if s[offset] == '"' and not parts[-1]:
382 if s[offset] == '"' and not parts[-1]:
383 return _parse_quote, parts, offset + 1
383 return _parse_quote, parts, offset + 1
384 elif s[offset] == '"' and parts[-1][-1] == '\\':
384 elif s[offset] == '"' and parts[-1][-1] == '\\':
385 parts[-1] = parts[-1][:-1] + s[offset]
385 parts[-1] = parts[-1][:-1] + s[offset]
386 return _parse_plain, parts, offset + 1
386 return _parse_plain, parts, offset + 1
387 parts[-1] += s[offset]
387 parts[-1] += s[offset]
388 return _parse_plain, parts, offset + 1
388 return _parse_plain, parts, offset + 1
389
389
390 def _parse_quote(parts, s, offset):
390 def _parse_quote(parts, s, offset):
391 if offset < len(s) and s[offset] == '"': # ""
391 if offset < len(s) and s[offset] == '"': # ""
392 parts.append('')
392 parts.append('')
393 offset += 1
393 offset += 1
394 while offset < len(s) and (s[offset].isspace() or
394 while offset < len(s) and (s[offset].isspace() or
395 s[offset] == ','):
395 s[offset] == ','):
396 offset += 1
396 offset += 1
397 return _parse_plain, parts, offset
397 return _parse_plain, parts, offset
398
398
399 while offset < len(s) and s[offset] != '"':
399 while offset < len(s) and s[offset] != '"':
400 if (s[offset] == '\\' and offset + 1 < len(s)
400 if (s[offset] == '\\' and offset + 1 < len(s)
401 and s[offset + 1] == '"'):
401 and s[offset + 1] == '"'):
402 offset += 1
402 offset += 1
403 parts[-1] += '"'
403 parts[-1] += '"'
404 else:
404 else:
405 parts[-1] += s[offset]
405 parts[-1] += s[offset]
406 offset += 1
406 offset += 1
407
407
408 if offset >= len(s):
408 if offset >= len(s):
409 real_parts = _configlist(parts[-1])
409 real_parts = _configlist(parts[-1])
410 if not real_parts:
410 if not real_parts:
411 parts[-1] = '"'
411 parts[-1] = '"'
412 else:
412 else:
413 real_parts[0] = '"' + real_parts[0]
413 real_parts[0] = '"' + real_parts[0]
414 parts = parts[:-1]
414 parts = parts[:-1]
415 parts.extend(real_parts)
415 parts.extend(real_parts)
416 return None, parts, offset
416 return None, parts, offset
417
417
418 offset += 1
418 offset += 1
419 while offset < len(s) and s[offset] in [' ', ',']:
419 while offset < len(s) and s[offset] in [' ', ',']:
420 offset += 1
420 offset += 1
421
421
422 if offset < len(s):
422 if offset < len(s):
423 if offset + 1 == len(s) and s[offset] == '"':
423 if offset + 1 == len(s) and s[offset] == '"':
424 parts[-1] += '"'
424 parts[-1] += '"'
425 offset += 1
425 offset += 1
426 else:
426 else:
427 parts.append('')
427 parts.append('')
428 else:
428 else:
429 return None, parts, offset
429 return None, parts, offset
430
430
431 return _parse_plain, parts, offset
431 return _parse_plain, parts, offset
432
432
433 def _configlist(s):
433 def _configlist(s):
434 s = s.rstrip(' ,')
434 s = s.rstrip(' ,')
435 if not s:
435 if not s:
436 return []
436 return []
437 parser, parts, offset = _parse_plain, [''], 0
437 parser, parts, offset = _parse_plain, [''], 0
438 while parser:
438 while parser:
439 parser, parts, offset = parser(parts, s, offset)
439 parser, parts, offset = parser(parts, s, offset)
440 return parts
440 return parts
441
441
442 result = self.config(section, name, untrusted=untrusted)
442 result = self.config(section, name, untrusted=untrusted)
443 if result is None:
443 if result is None:
444 result = default or []
444 result = default or []
445 if isinstance(result, basestring):
445 if isinstance(result, basestring):
446 result = _configlist(result.lstrip(' ,\n'))
446 result = _configlist(result.lstrip(' ,\n'))
447 if result is None:
447 if result is None:
448 result = default or []
448 result = default or []
449 return result
449 return result
450
450
451 def has_section(self, section, untrusted=False):
451 def has_section(self, section, untrusted=False):
452 '''tell whether section exists in config.'''
452 '''tell whether section exists in config.'''
453 return section in self._data(untrusted)
453 return section in self._data(untrusted)
454
454
455 def configitems(self, section, untrusted=False):
455 def configitems(self, section, untrusted=False):
456 items = self._data(untrusted).items(section)
456 items = self._data(untrusted).items(section)
457 if self.debugflag and not untrusted and self._reportuntrusted:
457 if self.debugflag and not untrusted and self._reportuntrusted:
458 for k, v in self._ucfg.items(section):
458 for k, v in self._ucfg.items(section):
459 if self._tcfg.get(section, k) != v:
459 if self._tcfg.get(section, k) != v:
460 self.debug("ignoring untrusted configuration option "
460 self.debug("ignoring untrusted configuration option "
461 "%s.%s = %s\n" % (section, k, v))
461 "%s.%s = %s\n" % (section, k, v))
462 return items
462 return items
463
463
464 def walkconfig(self, untrusted=False):
464 def walkconfig(self, untrusted=False):
465 cfg = self._data(untrusted)
465 cfg = self._data(untrusted)
466 for section in cfg.sections():
466 for section in cfg.sections():
467 for name, value in self.configitems(section, untrusted):
467 for name, value in self.configitems(section, untrusted):
468 yield section, name, value
468 yield section, name, value
469
469
470 def plain(self, feature=None):
470 def plain(self, feature=None):
471 '''is plain mode active?
471 '''is plain mode active?
472
472
473 Plain mode means that all configuration variables which affect
473 Plain mode means that all configuration variables which affect
474 the behavior and output of Mercurial should be
474 the behavior and output of Mercurial should be
475 ignored. Additionally, the output should be stable,
475 ignored. Additionally, the output should be stable,
476 reproducible and suitable for use in scripts or applications.
476 reproducible and suitable for use in scripts or applications.
477
477
478 The only way to trigger plain mode is by setting either the
478 The only way to trigger plain mode is by setting either the
479 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
479 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
480
480
481 The return value can either be
481 The return value can either be
482 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
482 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
483 - True otherwise
483 - True otherwise
484 '''
484 '''
485 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
485 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
486 return False
486 return False
487 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
487 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
488 if feature and exceptions:
488 if feature and exceptions:
489 return feature not in exceptions
489 return feature not in exceptions
490 return True
490 return True
491
491
492 def username(self):
492 def username(self):
493 """Return default username to be used in commits.
493 """Return default username to be used in commits.
494
494
495 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
495 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
496 and stop searching if one of these is set.
496 and stop searching if one of these is set.
497 If not found and ui.askusername is True, ask the user, else use
497 If not found and ui.askusername is True, ask the user, else use
498 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
498 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
499 """
499 """
500 user = os.environ.get("HGUSER")
500 user = os.environ.get("HGUSER")
501 if user is None:
501 if user is None:
502 user = self.config("ui", ["username", "user"])
502 user = self.config("ui", ["username", "user"])
503 if user is not None:
503 if user is not None:
504 user = os.path.expandvars(user)
504 user = os.path.expandvars(user)
505 if user is None:
505 if user is None:
506 user = os.environ.get("EMAIL")
506 user = os.environ.get("EMAIL")
507 if user is None and self.configbool("ui", "askusername"):
507 if user is None and self.configbool("ui", "askusername"):
508 user = self.prompt(_("enter a commit username:"), default=None)
508 user = self.prompt(_("enter a commit username:"), default=None)
509 if user is None and not self.interactive():
509 if user is None and not self.interactive():
510 try:
510 try:
511 user = '%s@%s' % (util.getuser(), socket.getfqdn())
511 user = '%s@%s' % (util.getuser(), socket.getfqdn())
512 self.warn(_("no username found, using '%s' instead\n") % user)
512 self.warn(_("no username found, using '%s' instead\n") % user)
513 except KeyError:
513 except KeyError:
514 pass
514 pass
515 if not user:
515 if not user:
516 raise util.Abort(_('no username supplied'),
516 raise util.Abort(_('no username supplied'),
517 hint=_('use "hg config --edit" '
517 hint=_('use "hg config --edit" '
518 'to set your username'))
518 'to set your username'))
519 if "\n" in user:
519 if "\n" in user:
520 raise util.Abort(_("username %s contains a newline\n") % repr(user))
520 raise util.Abort(_("username %s contains a newline\n") % repr(user))
521 return user
521 return user
522
522
523 def shortuser(self, user):
523 def shortuser(self, user):
524 """Return a short representation of a user name or email address."""
524 """Return a short representation of a user name or email address."""
525 if not self.verbose:
525 if not self.verbose:
526 user = util.shortuser(user)
526 user = util.shortuser(user)
527 return user
527 return user
528
528
529 def expandpath(self, loc, default=None):
529 def expandpath(self, loc, default=None):
530 """Return repository location relative to cwd or from [paths]"""
530 """Return repository location relative to cwd or from [paths]"""
531 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
531 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
532 return loc
532 return loc
533
533
534 path = self.config('paths', loc)
534 path = self.config('paths', loc)
535 if not path and default is not None:
535 if not path and default is not None:
536 path = self.config('paths', default)
536 path = self.config('paths', default)
537 return path or loc
537 return path or loc
538
538
539 def pushbuffer(self, error=False):
539 def pushbuffer(self, error=False):
540 """install a buffer to capture standar output of the ui object
540 """install a buffer to capture standar output of the ui object
541
541
542 If error is True, the error output will be captured too."""
542 If error is True, the error output will be captured too."""
543 self._buffers.append([])
543 self._buffers.append([])
544 self._bufferstates.append(error)
544 self._bufferstates.append(error)
545
545
546 def popbuffer(self, labeled=False):
546 def popbuffer(self, labeled=False):
547 '''pop the last buffer and return the buffered output
547 '''pop the last buffer and return the buffered output
548
548
549 If labeled is True, any labels associated with buffered
549 If labeled is True, any labels associated with buffered
550 output will be handled. By default, this has no effect
550 output will be handled. By default, this has no effect
551 on the output returned, but extensions and GUI tools may
551 on the output returned, but extensions and GUI tools may
552 handle this argument and returned styled output. If output
552 handle this argument and returned styled output. If output
553 is being buffered so it can be captured and parsed or
553 is being buffered so it can be captured and parsed or
554 processed, labeled should not be set to True.
554 processed, labeled should not be set to True.
555 '''
555 '''
556 self._bufferstates.pop()
556 self._bufferstates.pop()
557 return "".join(self._buffers.pop())
557 return "".join(self._buffers.pop())
558
558
559 def write(self, *args, **opts):
559 def write(self, *args, **opts):
560 '''write args to output
560 '''write args to output
561
561
562 By default, this method simply writes to the buffer or stdout,
562 By default, this method simply writes to the buffer or stdout,
563 but extensions or GUI tools may override this method,
563 but extensions or GUI tools may override this method,
564 write_err(), popbuffer(), and label() to style output from
564 write_err(), popbuffer(), and label() to style output from
565 various parts of hg.
565 various parts of hg.
566
566
567 An optional keyword argument, "label", can be passed in.
567 An optional keyword argument, "label", can be passed in.
568 This should be a string containing label names separated by
568 This should be a string containing label names separated by
569 space. Label names take the form of "topic.type". For example,
569 space. Label names take the form of "topic.type". For example,
570 ui.debug() issues a label of "ui.debug".
570 ui.debug() issues a label of "ui.debug".
571
571
572 When labeling output for a specific command, a label of
572 When labeling output for a specific command, a label of
573 "cmdname.type" is recommended. For example, status issues
573 "cmdname.type" is recommended. For example, status issues
574 a label of "status.modified" for modified files.
574 a label of "status.modified" for modified files.
575 '''
575 '''
576 if self._buffers:
576 if self._buffers:
577 self._buffers[-1].extend([str(a) for a in args])
577 self._buffers[-1].extend([str(a) for a in args])
578 else:
578 else:
579 for a in args:
579 for a in args:
580 self.fout.write(str(a))
580 self.fout.write(str(a))
581
581
582 def write_err(self, *args, **opts):
582 def write_err(self, *args, **opts):
583 try:
583 try:
584 if self._bufferstates and self._bufferstates[-1]:
584 if self._bufferstates and self._bufferstates[-1]:
585 return self.write(*args, **opts)
585 return self.write(*args, **opts)
586 if not getattr(self.fout, 'closed', False):
586 if not getattr(self.fout, 'closed', False):
587 self.fout.flush()
587 self.fout.flush()
588 for a in args:
588 for a in args:
589 self.ferr.write(str(a))
589 self.ferr.write(str(a))
590 # stderr may be buffered under win32 when redirected to files,
590 # stderr may be buffered under win32 when redirected to files,
591 # including stdout.
591 # including stdout.
592 if not getattr(self.ferr, 'closed', False):
592 if not getattr(self.ferr, 'closed', False):
593 self.ferr.flush()
593 self.ferr.flush()
594 except IOError, inst:
594 except IOError, inst:
595 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
595 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
596 raise
596 raise
597
597
598 def flush(self):
598 def flush(self):
599 try: self.fout.flush()
599 try: self.fout.flush()
600 except (IOError, ValueError): pass
600 except (IOError, ValueError): pass
601 try: self.ferr.flush()
601 try: self.ferr.flush()
602 except (IOError, ValueError): pass
602 except (IOError, ValueError): pass
603
603
604 def _isatty(self, fh):
604 def _isatty(self, fh):
605 if self.configbool('ui', 'nontty', False):
605 if self.configbool('ui', 'nontty', False):
606 return False
606 return False
607 return util.isatty(fh)
607 return util.isatty(fh)
608
608
609 def interactive(self):
609 def interactive(self):
610 '''is interactive input allowed?
610 '''is interactive input allowed?
611
611
612 An interactive session is a session where input can be reasonably read
612 An interactive session is a session where input can be reasonably read
613 from `sys.stdin'. If this function returns false, any attempt to read
613 from `sys.stdin'. If this function returns false, any attempt to read
614 from stdin should fail with an error, unless a sensible default has been
614 from stdin should fail with an error, unless a sensible default has been
615 specified.
615 specified.
616
616
617 Interactiveness is triggered by the value of the `ui.interactive'
617 Interactiveness is triggered by the value of the `ui.interactive'
618 configuration variable or - if it is unset - when `sys.stdin' points
618 configuration variable or - if it is unset - when `sys.stdin' points
619 to a terminal device.
619 to a terminal device.
620
620
621 This function refers to input only; for output, see `ui.formatted()'.
621 This function refers to input only; for output, see `ui.formatted()'.
622 '''
622 '''
623 i = self.configbool("ui", "interactive", None)
623 i = self.configbool("ui", "interactive", None)
624 if i is None:
624 if i is None:
625 # some environments replace stdin without implementing isatty
625 # some environments replace stdin without implementing isatty
626 # usually those are non-interactive
626 # usually those are non-interactive
627 return self._isatty(self.fin)
627 return self._isatty(self.fin)
628
628
629 return i
629 return i
630
630
631 def termwidth(self):
631 def termwidth(self):
632 '''how wide is the terminal in columns?
632 '''how wide is the terminal in columns?
633 '''
633 '''
634 if 'COLUMNS' in os.environ:
634 if 'COLUMNS' in os.environ:
635 try:
635 try:
636 return int(os.environ['COLUMNS'])
636 return int(os.environ['COLUMNS'])
637 except ValueError:
637 except ValueError:
638 pass
638 pass
639 return util.termwidth()
639 return util.termwidth()
640
640
641 def formatted(self):
641 def formatted(self):
642 '''should formatted output be used?
642 '''should formatted output be used?
643
643
644 It is often desirable to format the output to suite the output medium.
644 It is often desirable to format the output to suite the output medium.
645 Examples of this are truncating long lines or colorizing messages.
645 Examples of this are truncating long lines or colorizing messages.
646 However, this is not often not desirable when piping output into other
646 However, this is not often not desirable when piping output into other
647 utilities, e.g. `grep'.
647 utilities, e.g. `grep'.
648
648
649 Formatted output is triggered by the value of the `ui.formatted'
649 Formatted output is triggered by the value of the `ui.formatted'
650 configuration variable or - if it is unset - when `sys.stdout' points
650 configuration variable or - if it is unset - when `sys.stdout' points
651 to a terminal device. Please note that `ui.formatted' should be
651 to a terminal device. Please note that `ui.formatted' should be
652 considered an implementation detail; it is not intended for use outside
652 considered an implementation detail; it is not intended for use outside
653 Mercurial or its extensions.
653 Mercurial or its extensions.
654
654
655 This function refers to output only; for input, see `ui.interactive()'.
655 This function refers to output only; for input, see `ui.interactive()'.
656 This function always returns false when in plain mode, see `ui.plain()'.
656 This function always returns false when in plain mode, see `ui.plain()'.
657 '''
657 '''
658 if self.plain():
658 if self.plain():
659 return False
659 return False
660
660
661 i = self.configbool("ui", "formatted", None)
661 i = self.configbool("ui", "formatted", None)
662 if i is None:
662 if i is None:
663 # some environments replace stdout without implementing isatty
663 # some environments replace stdout without implementing isatty
664 # usually those are non-interactive
664 # usually those are non-interactive
665 return self._isatty(self.fout)
665 return self._isatty(self.fout)
666
666
667 return i
667 return i
668
668
669 def _readline(self, prompt=''):
669 def _readline(self, prompt=''):
670 if self._isatty(self.fin):
670 if self._isatty(self.fin):
671 try:
671 try:
672 # magically add command line editing support, where
672 # magically add command line editing support, where
673 # available
673 # available
674 import readline
674 import readline
675 # force demandimport to really load the module
675 # force demandimport to really load the module
676 readline.read_history_file
676 readline.read_history_file
677 # windows sometimes raises something other than ImportError
677 # windows sometimes raises something other than ImportError
678 except Exception:
678 except Exception:
679 pass
679 pass
680
680
681 # call write() so output goes through subclassed implementation
681 # call write() so output goes through subclassed implementation
682 # e.g. color extension on Windows
682 # e.g. color extension on Windows
683 self.write(prompt)
683 self.write(prompt)
684
684
685 # instead of trying to emulate raw_input, swap (self.fin,
685 # instead of trying to emulate raw_input, swap (self.fin,
686 # self.fout) with (sys.stdin, sys.stdout)
686 # self.fout) with (sys.stdin, sys.stdout)
687 oldin = sys.stdin
687 oldin = sys.stdin
688 oldout = sys.stdout
688 oldout = sys.stdout
689 sys.stdin = self.fin
689 sys.stdin = self.fin
690 sys.stdout = self.fout
690 sys.stdout = self.fout
691 # prompt ' ' must exist; otherwise readline may delete entire line
691 # prompt ' ' must exist; otherwise readline may delete entire line
692 # - http://bugs.python.org/issue12833
692 # - http://bugs.python.org/issue12833
693 line = raw_input(' ')
693 line = raw_input(' ')
694 sys.stdin = oldin
694 sys.stdin = oldin
695 sys.stdout = oldout
695 sys.stdout = oldout
696
696
697 # When stdin is in binary mode on Windows, it can cause
697 # When stdin is in binary mode on Windows, it can cause
698 # raw_input() to emit an extra trailing carriage return
698 # raw_input() to emit an extra trailing carriage return
699 if os.linesep == '\r\n' and line and line[-1] == '\r':
699 if os.linesep == '\r\n' and line and line[-1] == '\r':
700 line = line[:-1]
700 line = line[:-1]
701 return line
701 return line
702
702
703 def prompt(self, msg, default="y"):
703 def prompt(self, msg, default="y"):
704 """Prompt user with msg, read response.
704 """Prompt user with msg, read response.
705 If ui is not interactive, the default is returned.
705 If ui is not interactive, the default is returned.
706 """
706 """
707 if not self.interactive():
707 if not self.interactive():
708 self.write(msg, ' ', default, "\n")
708 self.write(msg, ' ', default, "\n")
709 return default
709 return default
710 try:
710 try:
711 r = self._readline(self.label(msg, 'ui.prompt'))
711 r = self._readline(self.label(msg, 'ui.prompt'))
712 if not r:
712 if not r:
713 r = default
713 r = default
714 # sometimes self.interactive disagrees with isatty,
714 if self.configbool('ui', 'promptecho'):
715 # show response provided on stdin when simulating
716 # but commandserver
717 if (not util.isatty(self.fin)
718 and not self.configbool('ui', 'nontty')):
719 self.write(r, "\n")
715 self.write(r, "\n")
720 return r
716 return r
721 except EOFError:
717 except EOFError:
722 raise util.Abort(_('response expected'))
718 raise util.Abort(_('response expected'))
723
719
724 @staticmethod
720 @staticmethod
725 def extractchoices(prompt):
721 def extractchoices(prompt):
726 """Extract prompt message and list of choices from specified prompt.
722 """Extract prompt message and list of choices from specified prompt.
727
723
728 This returns tuple "(message, choices)", and "choices" is the
724 This returns tuple "(message, choices)", and "choices" is the
729 list of tuple "(response character, text without &)".
725 list of tuple "(response character, text without &)".
730 """
726 """
731 parts = prompt.split('$$')
727 parts = prompt.split('$$')
732 msg = parts[0].rstrip(' ')
728 msg = parts[0].rstrip(' ')
733 choices = [p.strip(' ') for p in parts[1:]]
729 choices = [p.strip(' ') for p in parts[1:]]
734 return (msg,
730 return (msg,
735 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
731 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
736 for s in choices])
732 for s in choices])
737
733
738 def promptchoice(self, prompt, default=0):
734 def promptchoice(self, prompt, default=0):
739 """Prompt user with a message, read response, and ensure it matches
735 """Prompt user with a message, read response, and ensure it matches
740 one of the provided choices. The prompt is formatted as follows:
736 one of the provided choices. The prompt is formatted as follows:
741
737
742 "would you like fries with that (Yn)? $$ &Yes $$ &No"
738 "would you like fries with that (Yn)? $$ &Yes $$ &No"
743
739
744 The index of the choice is returned. Responses are case
740 The index of the choice is returned. Responses are case
745 insensitive. If ui is not interactive, the default is
741 insensitive. If ui is not interactive, the default is
746 returned.
742 returned.
747 """
743 """
748
744
749 msg, choices = self.extractchoices(prompt)
745 msg, choices = self.extractchoices(prompt)
750 resps = [r for r, t in choices]
746 resps = [r for r, t in choices]
751 while True:
747 while True:
752 r = self.prompt(msg, resps[default])
748 r = self.prompt(msg, resps[default])
753 if r.lower() in resps:
749 if r.lower() in resps:
754 return resps.index(r.lower())
750 return resps.index(r.lower())
755 self.write(_("unrecognized response\n"))
751 self.write(_("unrecognized response\n"))
756
752
757 def getpass(self, prompt=None, default=None):
753 def getpass(self, prompt=None, default=None):
758 if not self.interactive():
754 if not self.interactive():
759 return default
755 return default
760 try:
756 try:
761 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
757 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
762 # disable getpass() only if explicitly specified. it's still valid
758 # disable getpass() only if explicitly specified. it's still valid
763 # to interact with tty even if fin is not a tty.
759 # to interact with tty even if fin is not a tty.
764 if self.configbool('ui', 'nontty'):
760 if self.configbool('ui', 'nontty'):
765 return self.fin.readline().rstrip('\n')
761 return self.fin.readline().rstrip('\n')
766 else:
762 else:
767 return getpass.getpass('')
763 return getpass.getpass('')
768 except EOFError:
764 except EOFError:
769 raise util.Abort(_('response expected'))
765 raise util.Abort(_('response expected'))
770 def status(self, *msg, **opts):
766 def status(self, *msg, **opts):
771 '''write status message to output (if ui.quiet is False)
767 '''write status message to output (if ui.quiet is False)
772
768
773 This adds an output label of "ui.status".
769 This adds an output label of "ui.status".
774 '''
770 '''
775 if not self.quiet:
771 if not self.quiet:
776 opts['label'] = opts.get('label', '') + ' ui.status'
772 opts['label'] = opts.get('label', '') + ' ui.status'
777 self.write(*msg, **opts)
773 self.write(*msg, **opts)
778 def warn(self, *msg, **opts):
774 def warn(self, *msg, **opts):
779 '''write warning message to output (stderr)
775 '''write warning message to output (stderr)
780
776
781 This adds an output label of "ui.warning".
777 This adds an output label of "ui.warning".
782 '''
778 '''
783 opts['label'] = opts.get('label', '') + ' ui.warning'
779 opts['label'] = opts.get('label', '') + ' ui.warning'
784 self.write_err(*msg, **opts)
780 self.write_err(*msg, **opts)
785 def note(self, *msg, **opts):
781 def note(self, *msg, **opts):
786 '''write note to output (if ui.verbose is True)
782 '''write note to output (if ui.verbose is True)
787
783
788 This adds an output label of "ui.note".
784 This adds an output label of "ui.note".
789 '''
785 '''
790 if self.verbose:
786 if self.verbose:
791 opts['label'] = opts.get('label', '') + ' ui.note'
787 opts['label'] = opts.get('label', '') + ' ui.note'
792 self.write(*msg, **opts)
788 self.write(*msg, **opts)
793 def debug(self, *msg, **opts):
789 def debug(self, *msg, **opts):
794 '''write debug message to output (if ui.debugflag is True)
790 '''write debug message to output (if ui.debugflag is True)
795
791
796 This adds an output label of "ui.debug".
792 This adds an output label of "ui.debug".
797 '''
793 '''
798 if self.debugflag:
794 if self.debugflag:
799 opts['label'] = opts.get('label', '') + ' ui.debug'
795 opts['label'] = opts.get('label', '') + ' ui.debug'
800 self.write(*msg, **opts)
796 self.write(*msg, **opts)
801 def edit(self, text, user, extra={}, editform=None):
797 def edit(self, text, user, extra={}, editform=None):
802 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
798 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
803 text=True)
799 text=True)
804 try:
800 try:
805 f = os.fdopen(fd, "w")
801 f = os.fdopen(fd, "w")
806 f.write(text)
802 f.write(text)
807 f.close()
803 f.close()
808
804
809 environ = {'HGUSER': user}
805 environ = {'HGUSER': user}
810 if 'transplant_source' in extra:
806 if 'transplant_source' in extra:
811 environ.update({'HGREVISION': hex(extra['transplant_source'])})
807 environ.update({'HGREVISION': hex(extra['transplant_source'])})
812 for label in ('source', 'rebase_source'):
808 for label in ('source', 'rebase_source'):
813 if label in extra:
809 if label in extra:
814 environ.update({'HGREVISION': extra[label]})
810 environ.update({'HGREVISION': extra[label]})
815 break
811 break
816 if editform:
812 if editform:
817 environ.update({'HGEDITFORM': editform})
813 environ.update({'HGEDITFORM': editform})
818
814
819 editor = self.geteditor()
815 editor = self.geteditor()
820
816
821 util.system("%s \"%s\"" % (editor, name),
817 util.system("%s \"%s\"" % (editor, name),
822 environ=environ,
818 environ=environ,
823 onerr=util.Abort, errprefix=_("edit failed"),
819 onerr=util.Abort, errprefix=_("edit failed"),
824 out=self.fout)
820 out=self.fout)
825
821
826 f = open(name)
822 f = open(name)
827 t = f.read()
823 t = f.read()
828 f.close()
824 f.close()
829 finally:
825 finally:
830 os.unlink(name)
826 os.unlink(name)
831
827
832 return t
828 return t
833
829
834 def traceback(self, exc=None, force=False):
830 def traceback(self, exc=None, force=False):
835 '''print exception traceback if traceback printing enabled or forced.
831 '''print exception traceback if traceback printing enabled or forced.
836 only to call in exception handler. returns true if traceback
832 only to call in exception handler. returns true if traceback
837 printed.'''
833 printed.'''
838 if self.tracebackflag or force:
834 if self.tracebackflag or force:
839 if exc is None:
835 if exc is None:
840 exc = sys.exc_info()
836 exc = sys.exc_info()
841 cause = getattr(exc[1], 'cause', None)
837 cause = getattr(exc[1], 'cause', None)
842
838
843 if cause is not None:
839 if cause is not None:
844 causetb = traceback.format_tb(cause[2])
840 causetb = traceback.format_tb(cause[2])
845 exctb = traceback.format_tb(exc[2])
841 exctb = traceback.format_tb(exc[2])
846 exconly = traceback.format_exception_only(cause[0], cause[1])
842 exconly = traceback.format_exception_only(cause[0], cause[1])
847
843
848 # exclude frame where 'exc' was chained and rethrown from exctb
844 # exclude frame where 'exc' was chained and rethrown from exctb
849 self.write_err('Traceback (most recent call last):\n',
845 self.write_err('Traceback (most recent call last):\n',
850 ''.join(exctb[:-1]),
846 ''.join(exctb[:-1]),
851 ''.join(causetb),
847 ''.join(causetb),
852 ''.join(exconly))
848 ''.join(exconly))
853 else:
849 else:
854 traceback.print_exception(exc[0], exc[1], exc[2],
850 traceback.print_exception(exc[0], exc[1], exc[2],
855 file=self.ferr)
851 file=self.ferr)
856 return self.tracebackflag or force
852 return self.tracebackflag or force
857
853
858 def geteditor(self):
854 def geteditor(self):
859 '''return editor to use'''
855 '''return editor to use'''
860 if sys.platform == 'plan9':
856 if sys.platform == 'plan9':
861 # vi is the MIPS instruction simulator on Plan 9. We
857 # vi is the MIPS instruction simulator on Plan 9. We
862 # instead default to E to plumb commit messages to
858 # instead default to E to plumb commit messages to
863 # avoid confusion.
859 # avoid confusion.
864 editor = 'E'
860 editor = 'E'
865 else:
861 else:
866 editor = 'vi'
862 editor = 'vi'
867 return (os.environ.get("HGEDITOR") or
863 return (os.environ.get("HGEDITOR") or
868 self.config("ui", "editor") or
864 self.config("ui", "editor") or
869 os.environ.get("VISUAL") or
865 os.environ.get("VISUAL") or
870 os.environ.get("EDITOR", editor))
866 os.environ.get("EDITOR", editor))
871
867
872 def progress(self, topic, pos, item="", unit="", total=None):
868 def progress(self, topic, pos, item="", unit="", total=None):
873 '''show a progress message
869 '''show a progress message
874
870
875 With stock hg, this is simply a debug message that is hidden
871 With stock hg, this is simply a debug message that is hidden
876 by default, but with extensions or GUI tools it may be
872 by default, but with extensions or GUI tools it may be
877 visible. 'topic' is the current operation, 'item' is a
873 visible. 'topic' is the current operation, 'item' is a
878 non-numeric marker of the current position (i.e. the currently
874 non-numeric marker of the current position (i.e. the currently
879 in-process file), 'pos' is the current numeric position (i.e.
875 in-process file), 'pos' is the current numeric position (i.e.
880 revision, bytes, etc.), unit is a corresponding unit label,
876 revision, bytes, etc.), unit is a corresponding unit label,
881 and total is the highest expected pos.
877 and total is the highest expected pos.
882
878
883 Multiple nested topics may be active at a time.
879 Multiple nested topics may be active at a time.
884
880
885 All topics should be marked closed by setting pos to None at
881 All topics should be marked closed by setting pos to None at
886 termination.
882 termination.
887 '''
883 '''
888
884
889 if pos is None or not self.debugflag:
885 if pos is None or not self.debugflag:
890 return
886 return
891
887
892 if unit:
888 if unit:
893 unit = ' ' + unit
889 unit = ' ' + unit
894 if item:
890 if item:
895 item = ' ' + item
891 item = ' ' + item
896
892
897 if total:
893 if total:
898 pct = 100.0 * pos / total
894 pct = 100.0 * pos / total
899 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
895 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
900 % (topic, item, pos, total, unit, pct))
896 % (topic, item, pos, total, unit, pct))
901 else:
897 else:
902 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
898 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
903
899
904 def log(self, service, *msg, **opts):
900 def log(self, service, *msg, **opts):
905 '''hook for logging facility extensions
901 '''hook for logging facility extensions
906
902
907 service should be a readily-identifiable subsystem, which will
903 service should be a readily-identifiable subsystem, which will
908 allow filtering.
904 allow filtering.
909 message should be a newline-terminated string to log.
905 message should be a newline-terminated string to log.
910 '''
906 '''
911 pass
907 pass
912
908
913 def label(self, msg, label):
909 def label(self, msg, label):
914 '''style msg based on supplied label
910 '''style msg based on supplied label
915
911
916 Like ui.write(), this just returns msg unchanged, but extensions
912 Like ui.write(), this just returns msg unchanged, but extensions
917 and GUI tools can override it to allow styling output without
913 and GUI tools can override it to allow styling output without
918 writing it.
914 writing it.
919
915
920 ui.write(s, 'label') is equivalent to
916 ui.write(s, 'label') is equivalent to
921 ui.write(ui.label(s, 'label')).
917 ui.write(ui.label(s, 'label')).
922 '''
918 '''
923 return msg
919 return msg
@@ -1,1974 +1,1975 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 #
38 #
39 # (You could use any subset of the tests: test-s* happens to match
39 # (You could use any subset of the tests: test-s* happens to match
40 # enough that it's worth doing parallel runs, few enough that it
40 # enough that it's worth doing parallel runs, few enough that it
41 # completes fairly quickly, includes both shell and Python scripts, and
41 # completes fairly quickly, includes both shell and Python scripts, and
42 # includes some scripts that run daemon processes.)
42 # includes some scripts that run daemon processes.)
43
43
44 from distutils import version
44 from distutils import version
45 import difflib
45 import difflib
46 import errno
46 import errno
47 import optparse
47 import optparse
48 import os
48 import os
49 import shutil
49 import shutil
50 import subprocess
50 import subprocess
51 import signal
51 import signal
52 import sys
52 import sys
53 import tempfile
53 import tempfile
54 import time
54 import time
55 import random
55 import random
56 import re
56 import re
57 import threading
57 import threading
58 import killdaemons as killmod
58 import killdaemons as killmod
59 import Queue as queue
59 import Queue as queue
60 from xml.dom import minidom
60 from xml.dom import minidom
61 import unittest
61 import unittest
62
62
63 try:
63 try:
64 if sys.version_info < (2, 7):
64 if sys.version_info < (2, 7):
65 import simplejson as json
65 import simplejson as json
66 else:
66 else:
67 import json
67 import json
68 except ImportError:
68 except ImportError:
69 json = None
69 json = None
70
70
71 processlock = threading.Lock()
71 processlock = threading.Lock()
72
72
73 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
73 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
74 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
74 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
75 # zombies but it's pretty harmless even if we do.
75 # zombies but it's pretty harmless even if we do.
76 if sys.version_info < (2, 5):
76 if sys.version_info < (2, 5):
77 subprocess._cleanup = lambda: None
77 subprocess._cleanup = lambda: None
78
78
79 closefds = os.name == 'posix'
79 closefds = os.name == 'posix'
80 def Popen4(cmd, wd, timeout, env=None):
80 def Popen4(cmd, wd, timeout, env=None):
81 processlock.acquire()
81 processlock.acquire()
82 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
82 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
83 close_fds=closefds,
83 close_fds=closefds,
84 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
84 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
85 stderr=subprocess.STDOUT)
85 stderr=subprocess.STDOUT)
86 processlock.release()
86 processlock.release()
87
87
88 p.fromchild = p.stdout
88 p.fromchild = p.stdout
89 p.tochild = p.stdin
89 p.tochild = p.stdin
90 p.childerr = p.stderr
90 p.childerr = p.stderr
91
91
92 p.timeout = False
92 p.timeout = False
93 if timeout:
93 if timeout:
94 def t():
94 def t():
95 start = time.time()
95 start = time.time()
96 while time.time() - start < timeout and p.returncode is None:
96 while time.time() - start < timeout and p.returncode is None:
97 time.sleep(.1)
97 time.sleep(.1)
98 p.timeout = True
98 p.timeout = True
99 if p.returncode is None:
99 if p.returncode is None:
100 terminate(p)
100 terminate(p)
101 threading.Thread(target=t).start()
101 threading.Thread(target=t).start()
102
102
103 return p
103 return p
104
104
105 PYTHON = sys.executable.replace('\\', '/')
105 PYTHON = sys.executable.replace('\\', '/')
106 IMPL_PATH = 'PYTHONPATH'
106 IMPL_PATH = 'PYTHONPATH'
107 if 'java' in sys.platform:
107 if 'java' in sys.platform:
108 IMPL_PATH = 'JYTHONPATH'
108 IMPL_PATH = 'JYTHONPATH'
109
109
110 defaults = {
110 defaults = {
111 'jobs': ('HGTEST_JOBS', 1),
111 'jobs': ('HGTEST_JOBS', 1),
112 'timeout': ('HGTEST_TIMEOUT', 180),
112 'timeout': ('HGTEST_TIMEOUT', 180),
113 'port': ('HGTEST_PORT', 20059),
113 'port': ('HGTEST_PORT', 20059),
114 'shell': ('HGTEST_SHELL', 'sh'),
114 'shell': ('HGTEST_SHELL', 'sh'),
115 }
115 }
116
116
117 def parselistfiles(files, listtype, warn=True):
117 def parselistfiles(files, listtype, warn=True):
118 entries = dict()
118 entries = dict()
119 for filename in files:
119 for filename in files:
120 try:
120 try:
121 path = os.path.expanduser(os.path.expandvars(filename))
121 path = os.path.expanduser(os.path.expandvars(filename))
122 f = open(path, "rb")
122 f = open(path, "rb")
123 except IOError, err:
123 except IOError, err:
124 if err.errno != errno.ENOENT:
124 if err.errno != errno.ENOENT:
125 raise
125 raise
126 if warn:
126 if warn:
127 print "warning: no such %s file: %s" % (listtype, filename)
127 print "warning: no such %s file: %s" % (listtype, filename)
128 continue
128 continue
129
129
130 for line in f.readlines():
130 for line in f.readlines():
131 line = line.split('#', 1)[0].strip()
131 line = line.split('#', 1)[0].strip()
132 if line:
132 if line:
133 entries[line] = filename
133 entries[line] = filename
134
134
135 f.close()
135 f.close()
136 return entries
136 return entries
137
137
138 def getparser():
138 def getparser():
139 """Obtain the OptionParser used by the CLI."""
139 """Obtain the OptionParser used by the CLI."""
140 parser = optparse.OptionParser("%prog [options] [tests]")
140 parser = optparse.OptionParser("%prog [options] [tests]")
141
141
142 # keep these sorted
142 # keep these sorted
143 parser.add_option("--blacklist", action="append",
143 parser.add_option("--blacklist", action="append",
144 help="skip tests listed in the specified blacklist file")
144 help="skip tests listed in the specified blacklist file")
145 parser.add_option("--whitelist", action="append",
145 parser.add_option("--whitelist", action="append",
146 help="always run tests listed in the specified whitelist file")
146 help="always run tests listed in the specified whitelist file")
147 parser.add_option("--changed", type="string",
147 parser.add_option("--changed", type="string",
148 help="run tests that are changed in parent rev or working directory")
148 help="run tests that are changed in parent rev or working directory")
149 parser.add_option("-C", "--annotate", action="store_true",
149 parser.add_option("-C", "--annotate", action="store_true",
150 help="output files annotated with coverage")
150 help="output files annotated with coverage")
151 parser.add_option("-c", "--cover", action="store_true",
151 parser.add_option("-c", "--cover", action="store_true",
152 help="print a test coverage report")
152 help="print a test coverage report")
153 parser.add_option("-d", "--debug", action="store_true",
153 parser.add_option("-d", "--debug", action="store_true",
154 help="debug mode: write output of test scripts to console"
154 help="debug mode: write output of test scripts to console"
155 " rather than capturing and diffing it (disables timeout)")
155 " rather than capturing and diffing it (disables timeout)")
156 parser.add_option("-f", "--first", action="store_true",
156 parser.add_option("-f", "--first", action="store_true",
157 help="exit on the first test failure")
157 help="exit on the first test failure")
158 parser.add_option("-H", "--htmlcov", action="store_true",
158 parser.add_option("-H", "--htmlcov", action="store_true",
159 help="create an HTML report of the coverage of the files")
159 help="create an HTML report of the coverage of the files")
160 parser.add_option("-i", "--interactive", action="store_true",
160 parser.add_option("-i", "--interactive", action="store_true",
161 help="prompt to accept changed output")
161 help="prompt to accept changed output")
162 parser.add_option("-j", "--jobs", type="int",
162 parser.add_option("-j", "--jobs", type="int",
163 help="number of jobs to run in parallel"
163 help="number of jobs to run in parallel"
164 " (default: $%s or %d)" % defaults['jobs'])
164 " (default: $%s or %d)" % defaults['jobs'])
165 parser.add_option("--keep-tmpdir", action="store_true",
165 parser.add_option("--keep-tmpdir", action="store_true",
166 help="keep temporary directory after running tests")
166 help="keep temporary directory after running tests")
167 parser.add_option("-k", "--keywords",
167 parser.add_option("-k", "--keywords",
168 help="run tests matching keywords")
168 help="run tests matching keywords")
169 parser.add_option("-l", "--local", action="store_true",
169 parser.add_option("-l", "--local", action="store_true",
170 help="shortcut for --with-hg=<testdir>/../hg")
170 help="shortcut for --with-hg=<testdir>/../hg")
171 parser.add_option("--loop", action="store_true",
171 parser.add_option("--loop", action="store_true",
172 help="loop tests repeatedly")
172 help="loop tests repeatedly")
173 parser.add_option("-n", "--nodiff", action="store_true",
173 parser.add_option("-n", "--nodiff", action="store_true",
174 help="skip showing test changes")
174 help="skip showing test changes")
175 parser.add_option("-p", "--port", type="int",
175 parser.add_option("-p", "--port", type="int",
176 help="port on which servers should listen"
176 help="port on which servers should listen"
177 " (default: $%s or %d)" % defaults['port'])
177 " (default: $%s or %d)" % defaults['port'])
178 parser.add_option("--compiler", type="string",
178 parser.add_option("--compiler", type="string",
179 help="compiler to build with")
179 help="compiler to build with")
180 parser.add_option("--pure", action="store_true",
180 parser.add_option("--pure", action="store_true",
181 help="use pure Python code instead of C extensions")
181 help="use pure Python code instead of C extensions")
182 parser.add_option("-R", "--restart", action="store_true",
182 parser.add_option("-R", "--restart", action="store_true",
183 help="restart at last error")
183 help="restart at last error")
184 parser.add_option("-r", "--retest", action="store_true",
184 parser.add_option("-r", "--retest", action="store_true",
185 help="retest failed tests")
185 help="retest failed tests")
186 parser.add_option("-S", "--noskips", action="store_true",
186 parser.add_option("-S", "--noskips", action="store_true",
187 help="don't report skip tests verbosely")
187 help="don't report skip tests verbosely")
188 parser.add_option("--shell", type="string",
188 parser.add_option("--shell", type="string",
189 help="shell to use (default: $%s or %s)" % defaults['shell'])
189 help="shell to use (default: $%s or %s)" % defaults['shell'])
190 parser.add_option("-t", "--timeout", type="int",
190 parser.add_option("-t", "--timeout", type="int",
191 help="kill errant tests after TIMEOUT seconds"
191 help="kill errant tests after TIMEOUT seconds"
192 " (default: $%s or %d)" % defaults['timeout'])
192 " (default: $%s or %d)" % defaults['timeout'])
193 parser.add_option("--time", action="store_true",
193 parser.add_option("--time", action="store_true",
194 help="time how long each test takes")
194 help="time how long each test takes")
195 parser.add_option("--json", action="store_true",
195 parser.add_option("--json", action="store_true",
196 help="store test result data in 'report.json' file")
196 help="store test result data in 'report.json' file")
197 parser.add_option("--tmpdir", type="string",
197 parser.add_option("--tmpdir", type="string",
198 help="run tests in the given temporary directory"
198 help="run tests in the given temporary directory"
199 " (implies --keep-tmpdir)")
199 " (implies --keep-tmpdir)")
200 parser.add_option("-v", "--verbose", action="store_true",
200 parser.add_option("-v", "--verbose", action="store_true",
201 help="output verbose messages")
201 help="output verbose messages")
202 parser.add_option("--xunit", type="string",
202 parser.add_option("--xunit", type="string",
203 help="record xunit results at specified path")
203 help="record xunit results at specified path")
204 parser.add_option("--view", type="string",
204 parser.add_option("--view", type="string",
205 help="external diff viewer")
205 help="external diff viewer")
206 parser.add_option("--with-hg", type="string",
206 parser.add_option("--with-hg", type="string",
207 metavar="HG",
207 metavar="HG",
208 help="test using specified hg script rather than a "
208 help="test using specified hg script rather than a "
209 "temporary installation")
209 "temporary installation")
210 parser.add_option("-3", "--py3k-warnings", action="store_true",
210 parser.add_option("-3", "--py3k-warnings", action="store_true",
211 help="enable Py3k warnings on Python 2.6+")
211 help="enable Py3k warnings on Python 2.6+")
212 parser.add_option('--extra-config-opt', action="append",
212 parser.add_option('--extra-config-opt', action="append",
213 help='set the given config opt in the test hgrc')
213 help='set the given config opt in the test hgrc')
214 parser.add_option('--random', action="store_true",
214 parser.add_option('--random', action="store_true",
215 help='run tests in random order')
215 help='run tests in random order')
216
216
217 for option, (envvar, default) in defaults.items():
217 for option, (envvar, default) in defaults.items():
218 defaults[option] = type(default)(os.environ.get(envvar, default))
218 defaults[option] = type(default)(os.environ.get(envvar, default))
219 parser.set_defaults(**defaults)
219 parser.set_defaults(**defaults)
220
220
221 return parser
221 return parser
222
222
223 def parseargs(args, parser):
223 def parseargs(args, parser):
224 """Parse arguments with our OptionParser and validate results."""
224 """Parse arguments with our OptionParser and validate results."""
225 (options, args) = parser.parse_args(args)
225 (options, args) = parser.parse_args(args)
226
226
227 # jython is always pure
227 # jython is always pure
228 if 'java' in sys.platform or '__pypy__' in sys.modules:
228 if 'java' in sys.platform or '__pypy__' in sys.modules:
229 options.pure = True
229 options.pure = True
230
230
231 if options.with_hg:
231 if options.with_hg:
232 options.with_hg = os.path.expanduser(options.with_hg)
232 options.with_hg = os.path.expanduser(options.with_hg)
233 if not (os.path.isfile(options.with_hg) and
233 if not (os.path.isfile(options.with_hg) and
234 os.access(options.with_hg, os.X_OK)):
234 os.access(options.with_hg, os.X_OK)):
235 parser.error('--with-hg must specify an executable hg script')
235 parser.error('--with-hg must specify an executable hg script')
236 if not os.path.basename(options.with_hg) == 'hg':
236 if not os.path.basename(options.with_hg) == 'hg':
237 sys.stderr.write('warning: --with-hg should specify an hg script\n')
237 sys.stderr.write('warning: --with-hg should specify an hg script\n')
238 if options.local:
238 if options.local:
239 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
239 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
240 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
240 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
241 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
241 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
242 parser.error('--local specified, but %r not found or not executable'
242 parser.error('--local specified, but %r not found or not executable'
243 % hgbin)
243 % hgbin)
244 options.with_hg = hgbin
244 options.with_hg = hgbin
245
245
246 options.anycoverage = options.cover or options.annotate or options.htmlcov
246 options.anycoverage = options.cover or options.annotate or options.htmlcov
247 if options.anycoverage:
247 if options.anycoverage:
248 try:
248 try:
249 import coverage
249 import coverage
250 covver = version.StrictVersion(coverage.__version__).version
250 covver = version.StrictVersion(coverage.__version__).version
251 if covver < (3, 3):
251 if covver < (3, 3):
252 parser.error('coverage options require coverage 3.3 or later')
252 parser.error('coverage options require coverage 3.3 or later')
253 except ImportError:
253 except ImportError:
254 parser.error('coverage options now require the coverage package')
254 parser.error('coverage options now require the coverage package')
255
255
256 if options.anycoverage and options.local:
256 if options.anycoverage and options.local:
257 # this needs some path mangling somewhere, I guess
257 # this needs some path mangling somewhere, I guess
258 parser.error("sorry, coverage options do not work when --local "
258 parser.error("sorry, coverage options do not work when --local "
259 "is specified")
259 "is specified")
260
260
261 global verbose
261 global verbose
262 if options.verbose:
262 if options.verbose:
263 verbose = ''
263 verbose = ''
264
264
265 if options.tmpdir:
265 if options.tmpdir:
266 options.tmpdir = os.path.expanduser(options.tmpdir)
266 options.tmpdir = os.path.expanduser(options.tmpdir)
267
267
268 if options.jobs < 1:
268 if options.jobs < 1:
269 parser.error('--jobs must be positive')
269 parser.error('--jobs must be positive')
270 if options.interactive and options.debug:
270 if options.interactive and options.debug:
271 parser.error("-i/--interactive and -d/--debug are incompatible")
271 parser.error("-i/--interactive and -d/--debug are incompatible")
272 if options.debug:
272 if options.debug:
273 if options.timeout != defaults['timeout']:
273 if options.timeout != defaults['timeout']:
274 sys.stderr.write(
274 sys.stderr.write(
275 'warning: --timeout option ignored with --debug\n')
275 'warning: --timeout option ignored with --debug\n')
276 options.timeout = 0
276 options.timeout = 0
277 if options.py3k_warnings:
277 if options.py3k_warnings:
278 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
278 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
279 parser.error('--py3k-warnings can only be used on Python 2.6+')
279 parser.error('--py3k-warnings can only be used on Python 2.6+')
280 if options.blacklist:
280 if options.blacklist:
281 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
281 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
282 if options.whitelist:
282 if options.whitelist:
283 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
283 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
284 else:
284 else:
285 options.whitelisted = {}
285 options.whitelisted = {}
286
286
287 return (options, args)
287 return (options, args)
288
288
289 def rename(src, dst):
289 def rename(src, dst):
290 """Like os.rename(), trade atomicity and opened files friendliness
290 """Like os.rename(), trade atomicity and opened files friendliness
291 for existing destination support.
291 for existing destination support.
292 """
292 """
293 shutil.copy(src, dst)
293 shutil.copy(src, dst)
294 os.remove(src)
294 os.remove(src)
295
295
296 def getdiff(expected, output, ref, err):
296 def getdiff(expected, output, ref, err):
297 servefail = False
297 servefail = False
298 lines = []
298 lines = []
299 for line in difflib.unified_diff(expected, output, ref, err):
299 for line in difflib.unified_diff(expected, output, ref, err):
300 if line.startswith('+++') or line.startswith('---'):
300 if line.startswith('+++') or line.startswith('---'):
301 if line.endswith(' \n'):
301 if line.endswith(' \n'):
302 line = line[:-2] + '\n'
302 line = line[:-2] + '\n'
303 lines.append(line)
303 lines.append(line)
304 if not servefail and line.startswith(
304 if not servefail and line.startswith(
305 '+ abort: child process failed to start'):
305 '+ abort: child process failed to start'):
306 servefail = True
306 servefail = True
307
307
308 return servefail, lines
308 return servefail, lines
309
309
310 verbose = False
310 verbose = False
311 def vlog(*msg):
311 def vlog(*msg):
312 """Log only when in verbose mode."""
312 """Log only when in verbose mode."""
313 if verbose is False:
313 if verbose is False:
314 return
314 return
315
315
316 return log(*msg)
316 return log(*msg)
317
317
318 # Bytes that break XML even in a CDATA block: control characters 0-31
318 # Bytes that break XML even in a CDATA block: control characters 0-31
319 # sans \t, \n and \r
319 # sans \t, \n and \r
320 CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]")
320 CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]")
321
321
322 def cdatasafe(data):
322 def cdatasafe(data):
323 """Make a string safe to include in a CDATA block.
323 """Make a string safe to include in a CDATA block.
324
324
325 Certain control characters are illegal in a CDATA block, and
325 Certain control characters are illegal in a CDATA block, and
326 there's no way to include a ]]> in a CDATA either. This function
326 there's no way to include a ]]> in a CDATA either. This function
327 replaces illegal bytes with ? and adds a space between the ]] so
327 replaces illegal bytes with ? and adds a space between the ]] so
328 that it won't break the CDATA block.
328 that it won't break the CDATA block.
329 """
329 """
330 return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>')
330 return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>')
331
331
332 def log(*msg):
332 def log(*msg):
333 """Log something to stdout.
333 """Log something to stdout.
334
334
335 Arguments are strings to print.
335 Arguments are strings to print.
336 """
336 """
337 iolock.acquire()
337 iolock.acquire()
338 if verbose:
338 if verbose:
339 print verbose,
339 print verbose,
340 for m in msg:
340 for m in msg:
341 print m,
341 print m,
342 print
342 print
343 sys.stdout.flush()
343 sys.stdout.flush()
344 iolock.release()
344 iolock.release()
345
345
346 def terminate(proc):
346 def terminate(proc):
347 """Terminate subprocess (with fallback for Python versions < 2.6)"""
347 """Terminate subprocess (with fallback for Python versions < 2.6)"""
348 vlog('# Terminating process %d' % proc.pid)
348 vlog('# Terminating process %d' % proc.pid)
349 try:
349 try:
350 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
350 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
351 except OSError:
351 except OSError:
352 pass
352 pass
353
353
354 def killdaemons(pidfile):
354 def killdaemons(pidfile):
355 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
355 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
356 logfn=vlog)
356 logfn=vlog)
357
357
358 class Test(unittest.TestCase):
358 class Test(unittest.TestCase):
359 """Encapsulates a single, runnable test.
359 """Encapsulates a single, runnable test.
360
360
361 While this class conforms to the unittest.TestCase API, it differs in that
361 While this class conforms to the unittest.TestCase API, it differs in that
362 instances need to be instantiated manually. (Typically, unittest.TestCase
362 instances need to be instantiated manually. (Typically, unittest.TestCase
363 classes are instantiated automatically by scanning modules.)
363 classes are instantiated automatically by scanning modules.)
364 """
364 """
365
365
366 # Status code reserved for skipped tests (used by hghave).
366 # Status code reserved for skipped tests (used by hghave).
367 SKIPPED_STATUS = 80
367 SKIPPED_STATUS = 80
368
368
369 def __init__(self, path, tmpdir, keeptmpdir=False,
369 def __init__(self, path, tmpdir, keeptmpdir=False,
370 debug=False,
370 debug=False,
371 timeout=defaults['timeout'],
371 timeout=defaults['timeout'],
372 startport=defaults['port'], extraconfigopts=None,
372 startport=defaults['port'], extraconfigopts=None,
373 py3kwarnings=False, shell=None):
373 py3kwarnings=False, shell=None):
374 """Create a test from parameters.
374 """Create a test from parameters.
375
375
376 path is the full path to the file defining the test.
376 path is the full path to the file defining the test.
377
377
378 tmpdir is the main temporary directory to use for this test.
378 tmpdir is the main temporary directory to use for this test.
379
379
380 keeptmpdir determines whether to keep the test's temporary directory
380 keeptmpdir determines whether to keep the test's temporary directory
381 after execution. It defaults to removal (False).
381 after execution. It defaults to removal (False).
382
382
383 debug mode will make the test execute verbosely, with unfiltered
383 debug mode will make the test execute verbosely, with unfiltered
384 output.
384 output.
385
385
386 timeout controls the maximum run time of the test. It is ignored when
386 timeout controls the maximum run time of the test. It is ignored when
387 debug is True.
387 debug is True.
388
388
389 startport controls the starting port number to use for this test. Each
389 startport controls the starting port number to use for this test. Each
390 test will reserve 3 port numbers for execution. It is the caller's
390 test will reserve 3 port numbers for execution. It is the caller's
391 responsibility to allocate a non-overlapping port range to Test
391 responsibility to allocate a non-overlapping port range to Test
392 instances.
392 instances.
393
393
394 extraconfigopts is an iterable of extra hgrc config options. Values
394 extraconfigopts is an iterable of extra hgrc config options. Values
395 must have the form "key=value" (something understood by hgrc). Values
395 must have the form "key=value" (something understood by hgrc). Values
396 of the form "foo.key=value" will result in "[foo] key=value".
396 of the form "foo.key=value" will result in "[foo] key=value".
397
397
398 py3kwarnings enables Py3k warnings.
398 py3kwarnings enables Py3k warnings.
399
399
400 shell is the shell to execute tests in.
400 shell is the shell to execute tests in.
401 """
401 """
402
402
403 self.path = path
403 self.path = path
404 self.name = os.path.basename(path)
404 self.name = os.path.basename(path)
405 self._testdir = os.path.dirname(path)
405 self._testdir = os.path.dirname(path)
406 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
406 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
407
407
408 self._threadtmp = tmpdir
408 self._threadtmp = tmpdir
409 self._keeptmpdir = keeptmpdir
409 self._keeptmpdir = keeptmpdir
410 self._debug = debug
410 self._debug = debug
411 self._timeout = timeout
411 self._timeout = timeout
412 self._startport = startport
412 self._startport = startport
413 self._extraconfigopts = extraconfigopts or []
413 self._extraconfigopts = extraconfigopts or []
414 self._py3kwarnings = py3kwarnings
414 self._py3kwarnings = py3kwarnings
415 self._shell = shell
415 self._shell = shell
416
416
417 self._aborted = False
417 self._aborted = False
418 self._daemonpids = []
418 self._daemonpids = []
419 self._finished = None
419 self._finished = None
420 self._ret = None
420 self._ret = None
421 self._out = None
421 self._out = None
422 self._skipped = None
422 self._skipped = None
423 self._testtmp = None
423 self._testtmp = None
424
424
425 # If we're not in --debug mode and reference output file exists,
425 # If we're not in --debug mode and reference output file exists,
426 # check test output against it.
426 # check test output against it.
427 if debug:
427 if debug:
428 self._refout = None # to match "out is None"
428 self._refout = None # to match "out is None"
429 elif os.path.exists(self.refpath):
429 elif os.path.exists(self.refpath):
430 f = open(self.refpath, 'rb')
430 f = open(self.refpath, 'rb')
431 self._refout = f.read().splitlines(True)
431 self._refout = f.read().splitlines(True)
432 f.close()
432 f.close()
433 else:
433 else:
434 self._refout = []
434 self._refout = []
435
435
436 def __str__(self):
436 def __str__(self):
437 return self.name
437 return self.name
438
438
439 def shortDescription(self):
439 def shortDescription(self):
440 return self.name
440 return self.name
441
441
442 def setUp(self):
442 def setUp(self):
443 """Tasks to perform before run()."""
443 """Tasks to perform before run()."""
444 self._finished = False
444 self._finished = False
445 self._ret = None
445 self._ret = None
446 self._out = None
446 self._out = None
447 self._skipped = None
447 self._skipped = None
448
448
449 try:
449 try:
450 os.mkdir(self._threadtmp)
450 os.mkdir(self._threadtmp)
451 except OSError, e:
451 except OSError, e:
452 if e.errno != errno.EEXIST:
452 if e.errno != errno.EEXIST:
453 raise
453 raise
454
454
455 self._testtmp = os.path.join(self._threadtmp,
455 self._testtmp = os.path.join(self._threadtmp,
456 os.path.basename(self.path))
456 os.path.basename(self.path))
457 os.mkdir(self._testtmp)
457 os.mkdir(self._testtmp)
458
458
459 # Remove any previous output files.
459 # Remove any previous output files.
460 if os.path.exists(self.errpath):
460 if os.path.exists(self.errpath):
461 os.remove(self.errpath)
461 os.remove(self.errpath)
462
462
463 def run(self, result):
463 def run(self, result):
464 """Run this test and report results against a TestResult instance."""
464 """Run this test and report results against a TestResult instance."""
465 # This function is extremely similar to unittest.TestCase.run(). Once
465 # This function is extremely similar to unittest.TestCase.run(). Once
466 # we require Python 2.7 (or at least its version of unittest), this
466 # we require Python 2.7 (or at least its version of unittest), this
467 # function can largely go away.
467 # function can largely go away.
468 self._result = result
468 self._result = result
469 result.startTest(self)
469 result.startTest(self)
470 try:
470 try:
471 try:
471 try:
472 self.setUp()
472 self.setUp()
473 except (KeyboardInterrupt, SystemExit):
473 except (KeyboardInterrupt, SystemExit):
474 self._aborted = True
474 self._aborted = True
475 raise
475 raise
476 except Exception:
476 except Exception:
477 result.addError(self, sys.exc_info())
477 result.addError(self, sys.exc_info())
478 return
478 return
479
479
480 success = False
480 success = False
481 try:
481 try:
482 self.runTest()
482 self.runTest()
483 except KeyboardInterrupt:
483 except KeyboardInterrupt:
484 self._aborted = True
484 self._aborted = True
485 raise
485 raise
486 except SkipTest, e:
486 except SkipTest, e:
487 result.addSkip(self, str(e))
487 result.addSkip(self, str(e))
488 # The base class will have already counted this as a
488 # The base class will have already counted this as a
489 # test we "ran", but we want to exclude skipped tests
489 # test we "ran", but we want to exclude skipped tests
490 # from those we count towards those run.
490 # from those we count towards those run.
491 result.testsRun -= 1
491 result.testsRun -= 1
492 except IgnoreTest, e:
492 except IgnoreTest, e:
493 result.addIgnore(self, str(e))
493 result.addIgnore(self, str(e))
494 # As with skips, ignores also should be excluded from
494 # As with skips, ignores also should be excluded from
495 # the number of tests executed.
495 # the number of tests executed.
496 result.testsRun -= 1
496 result.testsRun -= 1
497 except WarnTest, e:
497 except WarnTest, e:
498 result.addWarn(self, str(e))
498 result.addWarn(self, str(e))
499 except self.failureException, e:
499 except self.failureException, e:
500 # This differs from unittest in that we don't capture
500 # This differs from unittest in that we don't capture
501 # the stack trace. This is for historical reasons and
501 # the stack trace. This is for historical reasons and
502 # this decision could be revisted in the future,
502 # this decision could be revisted in the future,
503 # especially for PythonTest instances.
503 # especially for PythonTest instances.
504 if result.addFailure(self, str(e)):
504 if result.addFailure(self, str(e)):
505 success = True
505 success = True
506 except Exception:
506 except Exception:
507 result.addError(self, sys.exc_info())
507 result.addError(self, sys.exc_info())
508 else:
508 else:
509 success = True
509 success = True
510
510
511 try:
511 try:
512 self.tearDown()
512 self.tearDown()
513 except (KeyboardInterrupt, SystemExit):
513 except (KeyboardInterrupt, SystemExit):
514 self._aborted = True
514 self._aborted = True
515 raise
515 raise
516 except Exception:
516 except Exception:
517 result.addError(self, sys.exc_info())
517 result.addError(self, sys.exc_info())
518 success = False
518 success = False
519
519
520 if success:
520 if success:
521 result.addSuccess(self)
521 result.addSuccess(self)
522 finally:
522 finally:
523 result.stopTest(self, interrupted=self._aborted)
523 result.stopTest(self, interrupted=self._aborted)
524
524
525 def runTest(self):
525 def runTest(self):
526 """Run this test instance.
526 """Run this test instance.
527
527
528 This will return a tuple describing the result of the test.
528 This will return a tuple describing the result of the test.
529 """
529 """
530 replacements = self._getreplacements()
530 replacements = self._getreplacements()
531 env = self._getenv()
531 env = self._getenv()
532 self._daemonpids.append(env['DAEMON_PIDS'])
532 self._daemonpids.append(env['DAEMON_PIDS'])
533 self._createhgrc(env['HGRCPATH'])
533 self._createhgrc(env['HGRCPATH'])
534
534
535 vlog('# Test', self.name)
535 vlog('# Test', self.name)
536
536
537 ret, out = self._run(replacements, env)
537 ret, out = self._run(replacements, env)
538 self._finished = True
538 self._finished = True
539 self._ret = ret
539 self._ret = ret
540 self._out = out
540 self._out = out
541
541
542 def describe(ret):
542 def describe(ret):
543 if ret < 0:
543 if ret < 0:
544 return 'killed by signal: %d' % -ret
544 return 'killed by signal: %d' % -ret
545 return 'returned error code %d' % ret
545 return 'returned error code %d' % ret
546
546
547 self._skipped = False
547 self._skipped = False
548
548
549 if ret == self.SKIPPED_STATUS:
549 if ret == self.SKIPPED_STATUS:
550 if out is None: # Debug mode, nothing to parse.
550 if out is None: # Debug mode, nothing to parse.
551 missing = ['unknown']
551 missing = ['unknown']
552 failed = None
552 failed = None
553 else:
553 else:
554 missing, failed = TTest.parsehghaveoutput(out)
554 missing, failed = TTest.parsehghaveoutput(out)
555
555
556 if not missing:
556 if not missing:
557 missing = ['skipped']
557 missing = ['skipped']
558
558
559 if failed:
559 if failed:
560 self.fail('hg have failed checking for %s' % failed[-1])
560 self.fail('hg have failed checking for %s' % failed[-1])
561 else:
561 else:
562 self._skipped = True
562 self._skipped = True
563 raise SkipTest(missing[-1])
563 raise SkipTest(missing[-1])
564 elif ret == 'timeout':
564 elif ret == 'timeout':
565 self.fail('timed out')
565 self.fail('timed out')
566 elif ret is False:
566 elif ret is False:
567 raise WarnTest('no result code from test')
567 raise WarnTest('no result code from test')
568 elif out != self._refout:
568 elif out != self._refout:
569 # Diff generation may rely on written .err file.
569 # Diff generation may rely on written .err file.
570 if (ret != 0 or out != self._refout) and not self._skipped \
570 if (ret != 0 or out != self._refout) and not self._skipped \
571 and not self._debug:
571 and not self._debug:
572 f = open(self.errpath, 'wb')
572 f = open(self.errpath, 'wb')
573 for line in out:
573 for line in out:
574 f.write(line)
574 f.write(line)
575 f.close()
575 f.close()
576
576
577 # The result object handles diff calculation for us.
577 # The result object handles diff calculation for us.
578 if self._result.addOutputMismatch(self, ret, out, self._refout):
578 if self._result.addOutputMismatch(self, ret, out, self._refout):
579 # change was accepted, skip failing
579 # change was accepted, skip failing
580 return
580 return
581
581
582 if ret:
582 if ret:
583 msg = 'output changed and ' + describe(ret)
583 msg = 'output changed and ' + describe(ret)
584 else:
584 else:
585 msg = 'output changed'
585 msg = 'output changed'
586
586
587 self.fail(msg)
587 self.fail(msg)
588 elif ret:
588 elif ret:
589 self.fail(describe(ret))
589 self.fail(describe(ret))
590
590
591 def tearDown(self):
591 def tearDown(self):
592 """Tasks to perform after run()."""
592 """Tasks to perform after run()."""
593 for entry in self._daemonpids:
593 for entry in self._daemonpids:
594 killdaemons(entry)
594 killdaemons(entry)
595 self._daemonpids = []
595 self._daemonpids = []
596
596
597 if not self._keeptmpdir:
597 if not self._keeptmpdir:
598 shutil.rmtree(self._testtmp, True)
598 shutil.rmtree(self._testtmp, True)
599 shutil.rmtree(self._threadtmp, True)
599 shutil.rmtree(self._threadtmp, True)
600
600
601 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
601 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
602 and not self._debug and self._out:
602 and not self._debug and self._out:
603 f = open(self.errpath, 'wb')
603 f = open(self.errpath, 'wb')
604 for line in self._out:
604 for line in self._out:
605 f.write(line)
605 f.write(line)
606 f.close()
606 f.close()
607
607
608 vlog("# Ret was:", self._ret)
608 vlog("# Ret was:", self._ret)
609
609
610 def _run(self, replacements, env):
610 def _run(self, replacements, env):
611 # This should be implemented in child classes to run tests.
611 # This should be implemented in child classes to run tests.
612 raise SkipTest('unknown test type')
612 raise SkipTest('unknown test type')
613
613
614 def abort(self):
614 def abort(self):
615 """Terminate execution of this test."""
615 """Terminate execution of this test."""
616 self._aborted = True
616 self._aborted = True
617
617
618 def _getreplacements(self):
618 def _getreplacements(self):
619 """Obtain a mapping of text replacements to apply to test output.
619 """Obtain a mapping of text replacements to apply to test output.
620
620
621 Test output needs to be normalized so it can be compared to expected
621 Test output needs to be normalized so it can be compared to expected
622 output. This function defines how some of that normalization will
622 output. This function defines how some of that normalization will
623 occur.
623 occur.
624 """
624 """
625 r = [
625 r = [
626 (r':%s\b' % self._startport, ':$HGPORT'),
626 (r':%s\b' % self._startport, ':$HGPORT'),
627 (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
627 (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
628 (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
628 (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
629 ]
629 ]
630
630
631 if os.name == 'nt':
631 if os.name == 'nt':
632 r.append(
632 r.append(
633 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
633 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
634 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
634 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
635 for c in self._testtmp), '$TESTTMP'))
635 for c in self._testtmp), '$TESTTMP'))
636 else:
636 else:
637 r.append((re.escape(self._testtmp), '$TESTTMP'))
637 r.append((re.escape(self._testtmp), '$TESTTMP'))
638
638
639 return r
639 return r
640
640
641 def _getenv(self):
641 def _getenv(self):
642 """Obtain environment variables to use during test execution."""
642 """Obtain environment variables to use during test execution."""
643 env = os.environ.copy()
643 env = os.environ.copy()
644 env['TESTTMP'] = self._testtmp
644 env['TESTTMP'] = self._testtmp
645 env['HOME'] = self._testtmp
645 env['HOME'] = self._testtmp
646 env["HGPORT"] = str(self._startport)
646 env["HGPORT"] = str(self._startport)
647 env["HGPORT1"] = str(self._startport + 1)
647 env["HGPORT1"] = str(self._startport + 1)
648 env["HGPORT2"] = str(self._startport + 2)
648 env["HGPORT2"] = str(self._startport + 2)
649 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
649 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
650 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
650 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
651 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
651 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
652 env["HGMERGE"] = "internal:merge"
652 env["HGMERGE"] = "internal:merge"
653 env["HGUSER"] = "test"
653 env["HGUSER"] = "test"
654 env["HGENCODING"] = "ascii"
654 env["HGENCODING"] = "ascii"
655 env["HGENCODINGMODE"] = "strict"
655 env["HGENCODINGMODE"] = "strict"
656
656
657 # Reset some environment variables to well-known values so that
657 # Reset some environment variables to well-known values so that
658 # the tests produce repeatable output.
658 # the tests produce repeatable output.
659 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
659 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
660 env['TZ'] = 'GMT'
660 env['TZ'] = 'GMT'
661 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
661 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
662 env['COLUMNS'] = '80'
662 env['COLUMNS'] = '80'
663 env['TERM'] = 'xterm'
663 env['TERM'] = 'xterm'
664
664
665 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
665 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
666 'NO_PROXY').split():
666 'NO_PROXY').split():
667 if k in env:
667 if k in env:
668 del env[k]
668 del env[k]
669
669
670 # unset env related to hooks
670 # unset env related to hooks
671 for k in env.keys():
671 for k in env.keys():
672 if k.startswith('HG_'):
672 if k.startswith('HG_'):
673 del env[k]
673 del env[k]
674
674
675 return env
675 return env
676
676
677 def _createhgrc(self, path):
677 def _createhgrc(self, path):
678 """Create an hgrc file for this test."""
678 """Create an hgrc file for this test."""
679 hgrc = open(path, 'wb')
679 hgrc = open(path, 'wb')
680 hgrc.write('[ui]\n')
680 hgrc.write('[ui]\n')
681 hgrc.write('slash = True\n')
681 hgrc.write('slash = True\n')
682 hgrc.write('interactive = False\n')
682 hgrc.write('interactive = False\n')
683 hgrc.write('mergemarkers = detailed\n')
683 hgrc.write('mergemarkers = detailed\n')
684 hgrc.write('promptecho = True\n')
684 hgrc.write('[defaults]\n')
685 hgrc.write('[defaults]\n')
685 hgrc.write('backout = -d "0 0"\n')
686 hgrc.write('backout = -d "0 0"\n')
686 hgrc.write('commit = -d "0 0"\n')
687 hgrc.write('commit = -d "0 0"\n')
687 hgrc.write('shelve = --date "0 0"\n')
688 hgrc.write('shelve = --date "0 0"\n')
688 hgrc.write('tag = -d "0 0"\n')
689 hgrc.write('tag = -d "0 0"\n')
689 for opt in self._extraconfigopts:
690 for opt in self._extraconfigopts:
690 section, key = opt.split('.', 1)
691 section, key = opt.split('.', 1)
691 assert '=' in key, ('extra config opt %s must '
692 assert '=' in key, ('extra config opt %s must '
692 'have an = for assignment' % opt)
693 'have an = for assignment' % opt)
693 hgrc.write('[%s]\n%s\n' % (section, key))
694 hgrc.write('[%s]\n%s\n' % (section, key))
694 hgrc.close()
695 hgrc.close()
695
696
696 def fail(self, msg):
697 def fail(self, msg):
697 # unittest differentiates between errored and failed.
698 # unittest differentiates between errored and failed.
698 # Failed is denoted by AssertionError (by default at least).
699 # Failed is denoted by AssertionError (by default at least).
699 raise AssertionError(msg)
700 raise AssertionError(msg)
700
701
701 class PythonTest(Test):
702 class PythonTest(Test):
702 """A Python-based test."""
703 """A Python-based test."""
703
704
704 @property
705 @property
705 def refpath(self):
706 def refpath(self):
706 return os.path.join(self._testdir, '%s.out' % self.name)
707 return os.path.join(self._testdir, '%s.out' % self.name)
707
708
708 def _run(self, replacements, env):
709 def _run(self, replacements, env):
709 py3kswitch = self._py3kwarnings and ' -3' or ''
710 py3kswitch = self._py3kwarnings and ' -3' or ''
710 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
711 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
711 vlog("# Running", cmd)
712 vlog("# Running", cmd)
712 if os.name == 'nt':
713 if os.name == 'nt':
713 replacements.append((r'\r\n', '\n'))
714 replacements.append((r'\r\n', '\n'))
714 result = run(cmd, self._testtmp, replacements, env,
715 result = run(cmd, self._testtmp, replacements, env,
715 debug=self._debug, timeout=self._timeout)
716 debug=self._debug, timeout=self._timeout)
716 if self._aborted:
717 if self._aborted:
717 raise KeyboardInterrupt()
718 raise KeyboardInterrupt()
718
719
719 return result
720 return result
720
721
721 class TTest(Test):
722 class TTest(Test):
722 """A "t test" is a test backed by a .t file."""
723 """A "t test" is a test backed by a .t file."""
723
724
724 SKIPPED_PREFIX = 'skipped: '
725 SKIPPED_PREFIX = 'skipped: '
725 FAILED_PREFIX = 'hghave check failed: '
726 FAILED_PREFIX = 'hghave check failed: '
726 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
727 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
727
728
728 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
729 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
729 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256))
730 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256))
730 ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'})
731 ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'})
731
732
732 @property
733 @property
733 def refpath(self):
734 def refpath(self):
734 return os.path.join(self._testdir, self.name)
735 return os.path.join(self._testdir, self.name)
735
736
736 def _run(self, replacements, env):
737 def _run(self, replacements, env):
737 f = open(self.path, 'rb')
738 f = open(self.path, 'rb')
738 lines = f.readlines()
739 lines = f.readlines()
739 f.close()
740 f.close()
740
741
741 salt, script, after, expected = self._parsetest(lines)
742 salt, script, after, expected = self._parsetest(lines)
742
743
743 # Write out the generated script.
744 # Write out the generated script.
744 fname = '%s.sh' % self._testtmp
745 fname = '%s.sh' % self._testtmp
745 f = open(fname, 'wb')
746 f = open(fname, 'wb')
746 for l in script:
747 for l in script:
747 f.write(l)
748 f.write(l)
748 f.close()
749 f.close()
749
750
750 cmd = '%s "%s"' % (self._shell, fname)
751 cmd = '%s "%s"' % (self._shell, fname)
751 vlog("# Running", cmd)
752 vlog("# Running", cmd)
752
753
753 exitcode, output = run(cmd, self._testtmp, replacements, env,
754 exitcode, output = run(cmd, self._testtmp, replacements, env,
754 debug=self._debug, timeout=self._timeout)
755 debug=self._debug, timeout=self._timeout)
755
756
756 if self._aborted:
757 if self._aborted:
757 raise KeyboardInterrupt()
758 raise KeyboardInterrupt()
758
759
759 # Do not merge output if skipped. Return hghave message instead.
760 # Do not merge output if skipped. Return hghave message instead.
760 # Similarly, with --debug, output is None.
761 # Similarly, with --debug, output is None.
761 if exitcode == self.SKIPPED_STATUS or output is None:
762 if exitcode == self.SKIPPED_STATUS or output is None:
762 return exitcode, output
763 return exitcode, output
763
764
764 return self._processoutput(exitcode, output, salt, after, expected)
765 return self._processoutput(exitcode, output, salt, after, expected)
765
766
766 def _hghave(self, reqs):
767 def _hghave(self, reqs):
767 # TODO do something smarter when all other uses of hghave are gone.
768 # TODO do something smarter when all other uses of hghave are gone.
768 tdir = self._testdir.replace('\\', '/')
769 tdir = self._testdir.replace('\\', '/')
769 proc = Popen4('%s -c "%s/hghave %s"' %
770 proc = Popen4('%s -c "%s/hghave %s"' %
770 (self._shell, tdir, ' '.join(reqs)),
771 (self._shell, tdir, ' '.join(reqs)),
771 self._testtmp, 0)
772 self._testtmp, 0)
772 stdout, stderr = proc.communicate()
773 stdout, stderr = proc.communicate()
773 ret = proc.wait()
774 ret = proc.wait()
774 if wifexited(ret):
775 if wifexited(ret):
775 ret = os.WEXITSTATUS(ret)
776 ret = os.WEXITSTATUS(ret)
776 if ret == 2:
777 if ret == 2:
777 print stdout
778 print stdout
778 sys.exit(1)
779 sys.exit(1)
779
780
780 return ret == 0
781 return ret == 0
781
782
782 def _parsetest(self, lines):
783 def _parsetest(self, lines):
783 # We generate a shell script which outputs unique markers to line
784 # We generate a shell script which outputs unique markers to line
784 # up script results with our source. These markers include input
785 # up script results with our source. These markers include input
785 # line number and the last return code.
786 # line number and the last return code.
786 salt = "SALT" + str(time.time())
787 salt = "SALT" + str(time.time())
787 def addsalt(line, inpython):
788 def addsalt(line, inpython):
788 if inpython:
789 if inpython:
789 script.append('%s %d 0\n' % (salt, line))
790 script.append('%s %d 0\n' % (salt, line))
790 else:
791 else:
791 script.append('echo %s %s $?\n' % (salt, line))
792 script.append('echo %s %s $?\n' % (salt, line))
792
793
793 script = []
794 script = []
794
795
795 # After we run the shell script, we re-unify the script output
796 # After we run the shell script, we re-unify the script output
796 # with non-active parts of the source, with synchronization by our
797 # with non-active parts of the source, with synchronization by our
797 # SALT line number markers. The after table contains the non-active
798 # SALT line number markers. The after table contains the non-active
798 # components, ordered by line number.
799 # components, ordered by line number.
799 after = {}
800 after = {}
800
801
801 # Expected shell script output.
802 # Expected shell script output.
802 expected = {}
803 expected = {}
803
804
804 pos = prepos = -1
805 pos = prepos = -1
805
806
806 # True or False when in a true or false conditional section
807 # True or False when in a true or false conditional section
807 skipping = None
808 skipping = None
808
809
809 # We keep track of whether or not we're in a Python block so we
810 # We keep track of whether or not we're in a Python block so we
810 # can generate the surrounding doctest magic.
811 # can generate the surrounding doctest magic.
811 inpython = False
812 inpython = False
812
813
813 if self._debug:
814 if self._debug:
814 script.append('set -x\n')
815 script.append('set -x\n')
815 if os.getenv('MSYSTEM'):
816 if os.getenv('MSYSTEM'):
816 script.append('alias pwd="pwd -W"\n')
817 script.append('alias pwd="pwd -W"\n')
817
818
818 for n, l in enumerate(lines):
819 for n, l in enumerate(lines):
819 if not l.endswith('\n'):
820 if not l.endswith('\n'):
820 l += '\n'
821 l += '\n'
821 if l.startswith('#require'):
822 if l.startswith('#require'):
822 lsplit = l.split()
823 lsplit = l.split()
823 if len(lsplit) < 2 or lsplit[0] != '#require':
824 if len(lsplit) < 2 or lsplit[0] != '#require':
824 after.setdefault(pos, []).append(' !!! invalid #require\n')
825 after.setdefault(pos, []).append(' !!! invalid #require\n')
825 if not self._hghave(lsplit[1:]):
826 if not self._hghave(lsplit[1:]):
826 script = ["exit 80\n"]
827 script = ["exit 80\n"]
827 break
828 break
828 after.setdefault(pos, []).append(l)
829 after.setdefault(pos, []).append(l)
829 elif l.startswith('#if'):
830 elif l.startswith('#if'):
830 lsplit = l.split()
831 lsplit = l.split()
831 if len(lsplit) < 2 or lsplit[0] != '#if':
832 if len(lsplit) < 2 or lsplit[0] != '#if':
832 after.setdefault(pos, []).append(' !!! invalid #if\n')
833 after.setdefault(pos, []).append(' !!! invalid #if\n')
833 if skipping is not None:
834 if skipping is not None:
834 after.setdefault(pos, []).append(' !!! nested #if\n')
835 after.setdefault(pos, []).append(' !!! nested #if\n')
835 skipping = not self._hghave(lsplit[1:])
836 skipping = not self._hghave(lsplit[1:])
836 after.setdefault(pos, []).append(l)
837 after.setdefault(pos, []).append(l)
837 elif l.startswith('#else'):
838 elif l.startswith('#else'):
838 if skipping is None:
839 if skipping is None:
839 after.setdefault(pos, []).append(' !!! missing #if\n')
840 after.setdefault(pos, []).append(' !!! missing #if\n')
840 skipping = not skipping
841 skipping = not skipping
841 after.setdefault(pos, []).append(l)
842 after.setdefault(pos, []).append(l)
842 elif l.startswith('#endif'):
843 elif l.startswith('#endif'):
843 if skipping is None:
844 if skipping is None:
844 after.setdefault(pos, []).append(' !!! missing #if\n')
845 after.setdefault(pos, []).append(' !!! missing #if\n')
845 skipping = None
846 skipping = None
846 after.setdefault(pos, []).append(l)
847 after.setdefault(pos, []).append(l)
847 elif skipping:
848 elif skipping:
848 after.setdefault(pos, []).append(l)
849 after.setdefault(pos, []).append(l)
849 elif l.startswith(' >>> '): # python inlines
850 elif l.startswith(' >>> '): # python inlines
850 after.setdefault(pos, []).append(l)
851 after.setdefault(pos, []).append(l)
851 prepos = pos
852 prepos = pos
852 pos = n
853 pos = n
853 if not inpython:
854 if not inpython:
854 # We've just entered a Python block. Add the header.
855 # We've just entered a Python block. Add the header.
855 inpython = True
856 inpython = True
856 addsalt(prepos, False) # Make sure we report the exit code.
857 addsalt(prepos, False) # Make sure we report the exit code.
857 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
858 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
858 addsalt(n, True)
859 addsalt(n, True)
859 script.append(l[2:])
860 script.append(l[2:])
860 elif l.startswith(' ... '): # python inlines
861 elif l.startswith(' ... '): # python inlines
861 after.setdefault(prepos, []).append(l)
862 after.setdefault(prepos, []).append(l)
862 script.append(l[2:])
863 script.append(l[2:])
863 elif l.startswith(' $ '): # commands
864 elif l.startswith(' $ '): # commands
864 if inpython:
865 if inpython:
865 script.append('EOF\n')
866 script.append('EOF\n')
866 inpython = False
867 inpython = False
867 after.setdefault(pos, []).append(l)
868 after.setdefault(pos, []).append(l)
868 prepos = pos
869 prepos = pos
869 pos = n
870 pos = n
870 addsalt(n, False)
871 addsalt(n, False)
871 cmd = l[4:].split()
872 cmd = l[4:].split()
872 if len(cmd) == 2 and cmd[0] == 'cd':
873 if len(cmd) == 2 and cmd[0] == 'cd':
873 l = ' $ cd %s || exit 1\n' % cmd[1]
874 l = ' $ cd %s || exit 1\n' % cmd[1]
874 script.append(l[4:])
875 script.append(l[4:])
875 elif l.startswith(' > '): # continuations
876 elif l.startswith(' > '): # continuations
876 after.setdefault(prepos, []).append(l)
877 after.setdefault(prepos, []).append(l)
877 script.append(l[4:])
878 script.append(l[4:])
878 elif l.startswith(' '): # results
879 elif l.startswith(' '): # results
879 # Queue up a list of expected results.
880 # Queue up a list of expected results.
880 expected.setdefault(pos, []).append(l[2:])
881 expected.setdefault(pos, []).append(l[2:])
881 else:
882 else:
882 if inpython:
883 if inpython:
883 script.append('EOF\n')
884 script.append('EOF\n')
884 inpython = False
885 inpython = False
885 # Non-command/result. Queue up for merged output.
886 # Non-command/result. Queue up for merged output.
886 after.setdefault(pos, []).append(l)
887 after.setdefault(pos, []).append(l)
887
888
888 if inpython:
889 if inpython:
889 script.append('EOF\n')
890 script.append('EOF\n')
890 if skipping is not None:
891 if skipping is not None:
891 after.setdefault(pos, []).append(' !!! missing #endif\n')
892 after.setdefault(pos, []).append(' !!! missing #endif\n')
892 addsalt(n + 1, False)
893 addsalt(n + 1, False)
893
894
894 return salt, script, after, expected
895 return salt, script, after, expected
895
896
896 def _processoutput(self, exitcode, output, salt, after, expected):
897 def _processoutput(self, exitcode, output, salt, after, expected):
897 # Merge the script output back into a unified test.
898 # Merge the script output back into a unified test.
898 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
899 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
899 if exitcode != 0:
900 if exitcode != 0:
900 warnonly = 3
901 warnonly = 3
901
902
902 pos = -1
903 pos = -1
903 postout = []
904 postout = []
904 for l in output:
905 for l in output:
905 lout, lcmd = l, None
906 lout, lcmd = l, None
906 if salt in l:
907 if salt in l:
907 lout, lcmd = l.split(salt, 1)
908 lout, lcmd = l.split(salt, 1)
908
909
909 if lout:
910 if lout:
910 if not lout.endswith('\n'):
911 if not lout.endswith('\n'):
911 lout += ' (no-eol)\n'
912 lout += ' (no-eol)\n'
912
913
913 # Find the expected output at the current position.
914 # Find the expected output at the current position.
914 el = None
915 el = None
915 if expected.get(pos, None):
916 if expected.get(pos, None):
916 el = expected[pos].pop(0)
917 el = expected[pos].pop(0)
917
918
918 r = TTest.linematch(el, lout)
919 r = TTest.linematch(el, lout)
919 if isinstance(r, str):
920 if isinstance(r, str):
920 if r == '+glob':
921 if r == '+glob':
921 lout = el[:-1] + ' (glob)\n'
922 lout = el[:-1] + ' (glob)\n'
922 r = '' # Warn only this line.
923 r = '' # Warn only this line.
923 elif r == '-glob':
924 elif r == '-glob':
924 lout = ''.join(el.rsplit(' (glob)', 1))
925 lout = ''.join(el.rsplit(' (glob)', 1))
925 r = '' # Warn only this line.
926 r = '' # Warn only this line.
926 else:
927 else:
927 log('\ninfo, unknown linematch result: %r\n' % r)
928 log('\ninfo, unknown linematch result: %r\n' % r)
928 r = False
929 r = False
929 if r:
930 if r:
930 postout.append(' ' + el)
931 postout.append(' ' + el)
931 else:
932 else:
932 if self.NEEDESCAPE(lout):
933 if self.NEEDESCAPE(lout):
933 lout = TTest._stringescape('%s (esc)\n' %
934 lout = TTest._stringescape('%s (esc)\n' %
934 lout.rstrip('\n'))
935 lout.rstrip('\n'))
935 postout.append(' ' + lout) # Let diff deal with it.
936 postout.append(' ' + lout) # Let diff deal with it.
936 if r != '': # If line failed.
937 if r != '': # If line failed.
937 warnonly = 3 # for sure not
938 warnonly = 3 # for sure not
938 elif warnonly == 1: # Is "not yet" and line is warn only.
939 elif warnonly == 1: # Is "not yet" and line is warn only.
939 warnonly = 2 # Yes do warn.
940 warnonly = 2 # Yes do warn.
940
941
941 if lcmd:
942 if lcmd:
942 # Add on last return code.
943 # Add on last return code.
943 ret = int(lcmd.split()[1])
944 ret = int(lcmd.split()[1])
944 if ret != 0:
945 if ret != 0:
945 postout.append(' [%s]\n' % ret)
946 postout.append(' [%s]\n' % ret)
946 if pos in after:
947 if pos in after:
947 # Merge in non-active test bits.
948 # Merge in non-active test bits.
948 postout += after.pop(pos)
949 postout += after.pop(pos)
949 pos = int(lcmd.split()[0])
950 pos = int(lcmd.split()[0])
950
951
951 if pos in after:
952 if pos in after:
952 postout += after.pop(pos)
953 postout += after.pop(pos)
953
954
954 if warnonly == 2:
955 if warnonly == 2:
955 exitcode = False # Set exitcode to warned.
956 exitcode = False # Set exitcode to warned.
956
957
957 return exitcode, postout
958 return exitcode, postout
958
959
959 @staticmethod
960 @staticmethod
960 def rematch(el, l):
961 def rematch(el, l):
961 try:
962 try:
962 # use \Z to ensure that the regex matches to the end of the string
963 # use \Z to ensure that the regex matches to the end of the string
963 if os.name == 'nt':
964 if os.name == 'nt':
964 return re.match(el + r'\r?\n\Z', l)
965 return re.match(el + r'\r?\n\Z', l)
965 return re.match(el + r'\n\Z', l)
966 return re.match(el + r'\n\Z', l)
966 except re.error:
967 except re.error:
967 # el is an invalid regex
968 # el is an invalid regex
968 return False
969 return False
969
970
970 @staticmethod
971 @staticmethod
971 def globmatch(el, l):
972 def globmatch(el, l):
972 # The only supported special characters are * and ? plus / which also
973 # The only supported special characters are * and ? plus / which also
973 # matches \ on windows. Escaping of these characters is supported.
974 # matches \ on windows. Escaping of these characters is supported.
974 if el + '\n' == l:
975 if el + '\n' == l:
975 if os.altsep:
976 if os.altsep:
976 # matching on "/" is not needed for this line
977 # matching on "/" is not needed for this line
977 return '-glob'
978 return '-glob'
978 return True
979 return True
979 i, n = 0, len(el)
980 i, n = 0, len(el)
980 res = ''
981 res = ''
981 while i < n:
982 while i < n:
982 c = el[i]
983 c = el[i]
983 i += 1
984 i += 1
984 if c == '\\' and el[i] in '*?\\/':
985 if c == '\\' and el[i] in '*?\\/':
985 res += el[i - 1:i + 1]
986 res += el[i - 1:i + 1]
986 i += 1
987 i += 1
987 elif c == '*':
988 elif c == '*':
988 res += '.*'
989 res += '.*'
989 elif c == '?':
990 elif c == '?':
990 res += '.'
991 res += '.'
991 elif c == '/' and os.altsep:
992 elif c == '/' and os.altsep:
992 res += '[/\\\\]'
993 res += '[/\\\\]'
993 else:
994 else:
994 res += re.escape(c)
995 res += re.escape(c)
995 return TTest.rematch(res, l)
996 return TTest.rematch(res, l)
996
997
997 @staticmethod
998 @staticmethod
998 def linematch(el, l):
999 def linematch(el, l):
999 if el == l: # perfect match (fast)
1000 if el == l: # perfect match (fast)
1000 return True
1001 return True
1001 if el:
1002 if el:
1002 if el.endswith(" (esc)\n"):
1003 if el.endswith(" (esc)\n"):
1003 el = el[:-7].decode('string-escape') + '\n'
1004 el = el[:-7].decode('string-escape') + '\n'
1004 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1005 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1005 return True
1006 return True
1006 if el.endswith(" (re)\n"):
1007 if el.endswith(" (re)\n"):
1007 return TTest.rematch(el[:-6], l)
1008 return TTest.rematch(el[:-6], l)
1008 if el.endswith(" (glob)\n"):
1009 if el.endswith(" (glob)\n"):
1009 return TTest.globmatch(el[:-8], l)
1010 return TTest.globmatch(el[:-8], l)
1010 if os.altsep and l.replace('\\', '/') == el:
1011 if os.altsep and l.replace('\\', '/') == el:
1011 return '+glob'
1012 return '+glob'
1012 return False
1013 return False
1013
1014
1014 @staticmethod
1015 @staticmethod
1015 def parsehghaveoutput(lines):
1016 def parsehghaveoutput(lines):
1016 '''Parse hghave log lines.
1017 '''Parse hghave log lines.
1017
1018
1018 Return tuple of lists (missing, failed):
1019 Return tuple of lists (missing, failed):
1019 * the missing/unknown features
1020 * the missing/unknown features
1020 * the features for which existence check failed'''
1021 * the features for which existence check failed'''
1021 missing = []
1022 missing = []
1022 failed = []
1023 failed = []
1023 for line in lines:
1024 for line in lines:
1024 if line.startswith(TTest.SKIPPED_PREFIX):
1025 if line.startswith(TTest.SKIPPED_PREFIX):
1025 line = line.splitlines()[0]
1026 line = line.splitlines()[0]
1026 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1027 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1027 elif line.startswith(TTest.FAILED_PREFIX):
1028 elif line.startswith(TTest.FAILED_PREFIX):
1028 line = line.splitlines()[0]
1029 line = line.splitlines()[0]
1029 failed.append(line[len(TTest.FAILED_PREFIX):])
1030 failed.append(line[len(TTest.FAILED_PREFIX):])
1030
1031
1031 return missing, failed
1032 return missing, failed
1032
1033
1033 @staticmethod
1034 @staticmethod
1034 def _escapef(m):
1035 def _escapef(m):
1035 return TTest.ESCAPEMAP[m.group(0)]
1036 return TTest.ESCAPEMAP[m.group(0)]
1036
1037
1037 @staticmethod
1038 @staticmethod
1038 def _stringescape(s):
1039 def _stringescape(s):
1039 return TTest.ESCAPESUB(TTest._escapef, s)
1040 return TTest.ESCAPESUB(TTest._escapef, s)
1040
1041
1041
1042
1042 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1043 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1043 def run(cmd, wd, replacements, env, debug=False, timeout=None):
1044 def run(cmd, wd, replacements, env, debug=False, timeout=None):
1044 """Run command in a sub-process, capturing the output (stdout and stderr).
1045 """Run command in a sub-process, capturing the output (stdout and stderr).
1045 Return a tuple (exitcode, output). output is None in debug mode."""
1046 Return a tuple (exitcode, output). output is None in debug mode."""
1046 if debug:
1047 if debug:
1047 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1048 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1048 ret = proc.wait()
1049 ret = proc.wait()
1049 return (ret, None)
1050 return (ret, None)
1050
1051
1051 proc = Popen4(cmd, wd, timeout, env)
1052 proc = Popen4(cmd, wd, timeout, env)
1052 def cleanup():
1053 def cleanup():
1053 terminate(proc)
1054 terminate(proc)
1054 ret = proc.wait()
1055 ret = proc.wait()
1055 if ret == 0:
1056 if ret == 0:
1056 ret = signal.SIGTERM << 8
1057 ret = signal.SIGTERM << 8
1057 killdaemons(env['DAEMON_PIDS'])
1058 killdaemons(env['DAEMON_PIDS'])
1058 return ret
1059 return ret
1059
1060
1060 output = ''
1061 output = ''
1061 proc.tochild.close()
1062 proc.tochild.close()
1062
1063
1063 try:
1064 try:
1064 output = proc.fromchild.read()
1065 output = proc.fromchild.read()
1065 except KeyboardInterrupt:
1066 except KeyboardInterrupt:
1066 vlog('# Handling keyboard interrupt')
1067 vlog('# Handling keyboard interrupt')
1067 cleanup()
1068 cleanup()
1068 raise
1069 raise
1069
1070
1070 ret = proc.wait()
1071 ret = proc.wait()
1071 if wifexited(ret):
1072 if wifexited(ret):
1072 ret = os.WEXITSTATUS(ret)
1073 ret = os.WEXITSTATUS(ret)
1073
1074
1074 if proc.timeout:
1075 if proc.timeout:
1075 ret = 'timeout'
1076 ret = 'timeout'
1076
1077
1077 if ret:
1078 if ret:
1078 killdaemons(env['DAEMON_PIDS'])
1079 killdaemons(env['DAEMON_PIDS'])
1079
1080
1080 for s, r in replacements:
1081 for s, r in replacements:
1081 output = re.sub(s, r, output)
1082 output = re.sub(s, r, output)
1082 return ret, output.splitlines(True)
1083 return ret, output.splitlines(True)
1083
1084
1084 iolock = threading.RLock()
1085 iolock = threading.RLock()
1085
1086
1086 class SkipTest(Exception):
1087 class SkipTest(Exception):
1087 """Raised to indicate that a test is to be skipped."""
1088 """Raised to indicate that a test is to be skipped."""
1088
1089
1089 class IgnoreTest(Exception):
1090 class IgnoreTest(Exception):
1090 """Raised to indicate that a test is to be ignored."""
1091 """Raised to indicate that a test is to be ignored."""
1091
1092
1092 class WarnTest(Exception):
1093 class WarnTest(Exception):
1093 """Raised to indicate that a test warned."""
1094 """Raised to indicate that a test warned."""
1094
1095
1095 class TestResult(unittest._TextTestResult):
1096 class TestResult(unittest._TextTestResult):
1096 """Holds results when executing via unittest."""
1097 """Holds results when executing via unittest."""
1097 # Don't worry too much about accessing the non-public _TextTestResult.
1098 # Don't worry too much about accessing the non-public _TextTestResult.
1098 # It is relatively common in Python testing tools.
1099 # It is relatively common in Python testing tools.
1099 def __init__(self, options, *args, **kwargs):
1100 def __init__(self, options, *args, **kwargs):
1100 super(TestResult, self).__init__(*args, **kwargs)
1101 super(TestResult, self).__init__(*args, **kwargs)
1101
1102
1102 self._options = options
1103 self._options = options
1103
1104
1104 # unittest.TestResult didn't have skipped until 2.7. We need to
1105 # unittest.TestResult didn't have skipped until 2.7. We need to
1105 # polyfill it.
1106 # polyfill it.
1106 self.skipped = []
1107 self.skipped = []
1107
1108
1108 # We have a custom "ignored" result that isn't present in any Python
1109 # We have a custom "ignored" result that isn't present in any Python
1109 # unittest implementation. It is very similar to skipped. It may make
1110 # unittest implementation. It is very similar to skipped. It may make
1110 # sense to map it into skip some day.
1111 # sense to map it into skip some day.
1111 self.ignored = []
1112 self.ignored = []
1112
1113
1113 # We have a custom "warned" result that isn't present in any Python
1114 # We have a custom "warned" result that isn't present in any Python
1114 # unittest implementation. It is very similar to failed. It may make
1115 # unittest implementation. It is very similar to failed. It may make
1115 # sense to map it into fail some day.
1116 # sense to map it into fail some day.
1116 self.warned = []
1117 self.warned = []
1117
1118
1118 self.times = []
1119 self.times = []
1119 self._started = {}
1120 self._started = {}
1120 self._stopped = {}
1121 self._stopped = {}
1121 # Data stored for the benefit of generating xunit reports.
1122 # Data stored for the benefit of generating xunit reports.
1122 self.successes = []
1123 self.successes = []
1123 self.faildata = {}
1124 self.faildata = {}
1124
1125
1125 def addFailure(self, test, reason):
1126 def addFailure(self, test, reason):
1126 self.failures.append((test, reason))
1127 self.failures.append((test, reason))
1127
1128
1128 if self._options.first:
1129 if self._options.first:
1129 self.stop()
1130 self.stop()
1130 else:
1131 else:
1131 iolock.acquire()
1132 iolock.acquire()
1132 if not self._options.nodiff:
1133 if not self._options.nodiff:
1133 self.stream.write('\nERROR: %s output changed\n' % test)
1134 self.stream.write('\nERROR: %s output changed\n' % test)
1134
1135
1135 self.stream.write('!')
1136 self.stream.write('!')
1136 self.stream.flush()
1137 self.stream.flush()
1137 iolock.release()
1138 iolock.release()
1138
1139
1139 def addSuccess(self, test):
1140 def addSuccess(self, test):
1140 iolock.acquire()
1141 iolock.acquire()
1141 super(TestResult, self).addSuccess(test)
1142 super(TestResult, self).addSuccess(test)
1142 iolock.release()
1143 iolock.release()
1143 self.successes.append(test)
1144 self.successes.append(test)
1144
1145
1145 def addError(self, test, err):
1146 def addError(self, test, err):
1146 super(TestResult, self).addError(test, err)
1147 super(TestResult, self).addError(test, err)
1147 if self._options.first:
1148 if self._options.first:
1148 self.stop()
1149 self.stop()
1149
1150
1150 # Polyfill.
1151 # Polyfill.
1151 def addSkip(self, test, reason):
1152 def addSkip(self, test, reason):
1152 self.skipped.append((test, reason))
1153 self.skipped.append((test, reason))
1153 iolock.acquire()
1154 iolock.acquire()
1154 if self.showAll:
1155 if self.showAll:
1155 self.stream.writeln('skipped %s' % reason)
1156 self.stream.writeln('skipped %s' % reason)
1156 else:
1157 else:
1157 self.stream.write('s')
1158 self.stream.write('s')
1158 self.stream.flush()
1159 self.stream.flush()
1159 iolock.release()
1160 iolock.release()
1160
1161
1161 def addIgnore(self, test, reason):
1162 def addIgnore(self, test, reason):
1162 self.ignored.append((test, reason))
1163 self.ignored.append((test, reason))
1163 iolock.acquire()
1164 iolock.acquire()
1164 if self.showAll:
1165 if self.showAll:
1165 self.stream.writeln('ignored %s' % reason)
1166 self.stream.writeln('ignored %s' % reason)
1166 else:
1167 else:
1167 if reason != 'not retesting' and reason != "doesn't match keyword":
1168 if reason != 'not retesting' and reason != "doesn't match keyword":
1168 self.stream.write('i')
1169 self.stream.write('i')
1169 else:
1170 else:
1170 self.testsRun += 1
1171 self.testsRun += 1
1171 self.stream.flush()
1172 self.stream.flush()
1172 iolock.release()
1173 iolock.release()
1173
1174
1174 def addWarn(self, test, reason):
1175 def addWarn(self, test, reason):
1175 self.warned.append((test, reason))
1176 self.warned.append((test, reason))
1176
1177
1177 if self._options.first:
1178 if self._options.first:
1178 self.stop()
1179 self.stop()
1179
1180
1180 iolock.acquire()
1181 iolock.acquire()
1181 if self.showAll:
1182 if self.showAll:
1182 self.stream.writeln('warned %s' % reason)
1183 self.stream.writeln('warned %s' % reason)
1183 else:
1184 else:
1184 self.stream.write('~')
1185 self.stream.write('~')
1185 self.stream.flush()
1186 self.stream.flush()
1186 iolock.release()
1187 iolock.release()
1187
1188
1188 def addOutputMismatch(self, test, ret, got, expected):
1189 def addOutputMismatch(self, test, ret, got, expected):
1189 """Record a mismatch in test output for a particular test."""
1190 """Record a mismatch in test output for a particular test."""
1190 if self.shouldStop:
1191 if self.shouldStop:
1191 # don't print, some other test case already failed and
1192 # don't print, some other test case already failed and
1192 # printed, we're just stale and probably failed due to our
1193 # printed, we're just stale and probably failed due to our
1193 # temp dir getting cleaned up.
1194 # temp dir getting cleaned up.
1194 return
1195 return
1195
1196
1196 accepted = False
1197 accepted = False
1197 failed = False
1198 failed = False
1198 lines = []
1199 lines = []
1199
1200
1200 iolock.acquire()
1201 iolock.acquire()
1201 if self._options.nodiff:
1202 if self._options.nodiff:
1202 pass
1203 pass
1203 elif self._options.view:
1204 elif self._options.view:
1204 os.system("%s %s %s" %
1205 os.system("%s %s %s" %
1205 (self._options.view, test.refpath, test.errpath))
1206 (self._options.view, test.refpath, test.errpath))
1206 else:
1207 else:
1207 servefail, lines = getdiff(expected, got,
1208 servefail, lines = getdiff(expected, got,
1208 test.refpath, test.errpath)
1209 test.refpath, test.errpath)
1209 if servefail:
1210 if servefail:
1210 self.addFailure(
1211 self.addFailure(
1211 test,
1212 test,
1212 'server failed to start (HGPORT=%s)' % test._startport)
1213 'server failed to start (HGPORT=%s)' % test._startport)
1213 else:
1214 else:
1214 self.stream.write('\n')
1215 self.stream.write('\n')
1215 for line in lines:
1216 for line in lines:
1216 self.stream.write(line)
1217 self.stream.write(line)
1217 self.stream.flush()
1218 self.stream.flush()
1218
1219
1219 # handle interactive prompt without releasing iolock
1220 # handle interactive prompt without releasing iolock
1220 if self._options.interactive:
1221 if self._options.interactive:
1221 self.stream.write('Accept this change? [n] ')
1222 self.stream.write('Accept this change? [n] ')
1222 answer = sys.stdin.readline().strip()
1223 answer = sys.stdin.readline().strip()
1223 if answer.lower() in ('y', 'yes'):
1224 if answer.lower() in ('y', 'yes'):
1224 if test.name.endswith('.t'):
1225 if test.name.endswith('.t'):
1225 rename(test.errpath, test.path)
1226 rename(test.errpath, test.path)
1226 else:
1227 else:
1227 rename(test.errpath, '%s.out' % test.path)
1228 rename(test.errpath, '%s.out' % test.path)
1228 accepted = True
1229 accepted = True
1229 if not accepted and not failed:
1230 if not accepted and not failed:
1230 self.faildata[test.name] = ''.join(lines)
1231 self.faildata[test.name] = ''.join(lines)
1231 iolock.release()
1232 iolock.release()
1232
1233
1233 return accepted
1234 return accepted
1234
1235
1235 def startTest(self, test):
1236 def startTest(self, test):
1236 super(TestResult, self).startTest(test)
1237 super(TestResult, self).startTest(test)
1237
1238
1238 # os.times module computes the user time and system time spent by
1239 # os.times module computes the user time and system time spent by
1239 # child's processes along with real elapsed time taken by a process.
1240 # child's processes along with real elapsed time taken by a process.
1240 # This module has one limitation. It can only work for Linux user
1241 # This module has one limitation. It can only work for Linux user
1241 # and not for Windows.
1242 # and not for Windows.
1242 self._started[test.name] = os.times()
1243 self._started[test.name] = os.times()
1243
1244
1244 def stopTest(self, test, interrupted=False):
1245 def stopTest(self, test, interrupted=False):
1245 super(TestResult, self).stopTest(test)
1246 super(TestResult, self).stopTest(test)
1246
1247
1247 self._stopped[test.name] = os.times()
1248 self._stopped[test.name] = os.times()
1248
1249
1249 starttime = self._started[test.name]
1250 starttime = self._started[test.name]
1250 endtime = self._stopped[test.name]
1251 endtime = self._stopped[test.name]
1251 self.times.append((test.name, endtime[2] - starttime[2],
1252 self.times.append((test.name, endtime[2] - starttime[2],
1252 endtime[3] - starttime[3], endtime[4] - starttime[4]))
1253 endtime[3] - starttime[3], endtime[4] - starttime[4]))
1253
1254
1254 del self._started[test.name]
1255 del self._started[test.name]
1255 del self._stopped[test.name]
1256 del self._stopped[test.name]
1256
1257
1257 if interrupted:
1258 if interrupted:
1258 iolock.acquire()
1259 iolock.acquire()
1259 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1260 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1260 test.name, self.times[-1][3]))
1261 test.name, self.times[-1][3]))
1261 iolock.release()
1262 iolock.release()
1262
1263
1263 class TestSuite(unittest.TestSuite):
1264 class TestSuite(unittest.TestSuite):
1264 """Custom unitest TestSuite that knows how to execute Mercurial tests."""
1265 """Custom unitest TestSuite that knows how to execute Mercurial tests."""
1265
1266
1266 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1267 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1267 retest=False, keywords=None, loop=False,
1268 retest=False, keywords=None, loop=False,
1268 *args, **kwargs):
1269 *args, **kwargs):
1269 """Create a new instance that can run tests with a configuration.
1270 """Create a new instance that can run tests with a configuration.
1270
1271
1271 testdir specifies the directory where tests are executed from. This
1272 testdir specifies the directory where tests are executed from. This
1272 is typically the ``tests`` directory from Mercurial's source
1273 is typically the ``tests`` directory from Mercurial's source
1273 repository.
1274 repository.
1274
1275
1275 jobs specifies the number of jobs to run concurrently. Each test
1276 jobs specifies the number of jobs to run concurrently. Each test
1276 executes on its own thread. Tests actually spawn new processes, so
1277 executes on its own thread. Tests actually spawn new processes, so
1277 state mutation should not be an issue.
1278 state mutation should not be an issue.
1278
1279
1279 whitelist and blacklist denote tests that have been whitelisted and
1280 whitelist and blacklist denote tests that have been whitelisted and
1280 blacklisted, respectively. These arguments don't belong in TestSuite.
1281 blacklisted, respectively. These arguments don't belong in TestSuite.
1281 Instead, whitelist and blacklist should be handled by the thing that
1282 Instead, whitelist and blacklist should be handled by the thing that
1282 populates the TestSuite with tests. They are present to preserve
1283 populates the TestSuite with tests. They are present to preserve
1283 backwards compatible behavior which reports skipped tests as part
1284 backwards compatible behavior which reports skipped tests as part
1284 of the results.
1285 of the results.
1285
1286
1286 retest denotes whether to retest failed tests. This arguably belongs
1287 retest denotes whether to retest failed tests. This arguably belongs
1287 outside of TestSuite.
1288 outside of TestSuite.
1288
1289
1289 keywords denotes key words that will be used to filter which tests
1290 keywords denotes key words that will be used to filter which tests
1290 to execute. This arguably belongs outside of TestSuite.
1291 to execute. This arguably belongs outside of TestSuite.
1291
1292
1292 loop denotes whether to loop over tests forever.
1293 loop denotes whether to loop over tests forever.
1293 """
1294 """
1294 super(TestSuite, self).__init__(*args, **kwargs)
1295 super(TestSuite, self).__init__(*args, **kwargs)
1295
1296
1296 self._jobs = jobs
1297 self._jobs = jobs
1297 self._whitelist = whitelist
1298 self._whitelist = whitelist
1298 self._blacklist = blacklist
1299 self._blacklist = blacklist
1299 self._retest = retest
1300 self._retest = retest
1300 self._keywords = keywords
1301 self._keywords = keywords
1301 self._loop = loop
1302 self._loop = loop
1302
1303
1303 def run(self, result):
1304 def run(self, result):
1304 # We have a number of filters that need to be applied. We do this
1305 # We have a number of filters that need to be applied. We do this
1305 # here instead of inside Test because it makes the running logic for
1306 # here instead of inside Test because it makes the running logic for
1306 # Test simpler.
1307 # Test simpler.
1307 tests = []
1308 tests = []
1308 for test in self._tests:
1309 for test in self._tests:
1309 if not os.path.exists(test.path):
1310 if not os.path.exists(test.path):
1310 result.addSkip(test, "Doesn't exist")
1311 result.addSkip(test, "Doesn't exist")
1311 continue
1312 continue
1312
1313
1313 if not (self._whitelist and test.name in self._whitelist):
1314 if not (self._whitelist and test.name in self._whitelist):
1314 if self._blacklist and test.name in self._blacklist:
1315 if self._blacklist and test.name in self._blacklist:
1315 result.addSkip(test, 'blacklisted')
1316 result.addSkip(test, 'blacklisted')
1316 continue
1317 continue
1317
1318
1318 if self._retest and not os.path.exists(test.errpath):
1319 if self._retest and not os.path.exists(test.errpath):
1319 result.addIgnore(test, 'not retesting')
1320 result.addIgnore(test, 'not retesting')
1320 continue
1321 continue
1321
1322
1322 if self._keywords:
1323 if self._keywords:
1323 f = open(test.path, 'rb')
1324 f = open(test.path, 'rb')
1324 t = f.read().lower() + test.name.lower()
1325 t = f.read().lower() + test.name.lower()
1325 f.close()
1326 f.close()
1326 ignored = False
1327 ignored = False
1327 for k in self._keywords.lower().split():
1328 for k in self._keywords.lower().split():
1328 if k not in t:
1329 if k not in t:
1329 result.addIgnore(test, "doesn't match keyword")
1330 result.addIgnore(test, "doesn't match keyword")
1330 ignored = True
1331 ignored = True
1331 break
1332 break
1332
1333
1333 if ignored:
1334 if ignored:
1334 continue
1335 continue
1335
1336
1336 tests.append(test)
1337 tests.append(test)
1337
1338
1338 runtests = list(tests)
1339 runtests = list(tests)
1339 done = queue.Queue()
1340 done = queue.Queue()
1340 running = 0
1341 running = 0
1341
1342
1342 def job(test, result):
1343 def job(test, result):
1343 try:
1344 try:
1344 test(result)
1345 test(result)
1345 done.put(None)
1346 done.put(None)
1346 except KeyboardInterrupt:
1347 except KeyboardInterrupt:
1347 pass
1348 pass
1348 except: # re-raises
1349 except: # re-raises
1349 done.put(('!', test, 'run-test raised an error, see traceback'))
1350 done.put(('!', test, 'run-test raised an error, see traceback'))
1350 raise
1351 raise
1351
1352
1352 try:
1353 try:
1353 while tests or running:
1354 while tests or running:
1354 if not done.empty() or running == self._jobs or not tests:
1355 if not done.empty() or running == self._jobs or not tests:
1355 try:
1356 try:
1356 done.get(True, 1)
1357 done.get(True, 1)
1357 if result and result.shouldStop:
1358 if result and result.shouldStop:
1358 break
1359 break
1359 except queue.Empty:
1360 except queue.Empty:
1360 continue
1361 continue
1361 running -= 1
1362 running -= 1
1362 if tests and not running == self._jobs:
1363 if tests and not running == self._jobs:
1363 test = tests.pop(0)
1364 test = tests.pop(0)
1364 if self._loop:
1365 if self._loop:
1365 tests.append(test)
1366 tests.append(test)
1366 t = threading.Thread(target=job, name=test.name,
1367 t = threading.Thread(target=job, name=test.name,
1367 args=(test, result))
1368 args=(test, result))
1368 t.start()
1369 t.start()
1369 running += 1
1370 running += 1
1370 except KeyboardInterrupt:
1371 except KeyboardInterrupt:
1371 for test in runtests:
1372 for test in runtests:
1372 test.abort()
1373 test.abort()
1373
1374
1374 return result
1375 return result
1375
1376
1376 class TextTestRunner(unittest.TextTestRunner):
1377 class TextTestRunner(unittest.TextTestRunner):
1377 """Custom unittest test runner that uses appropriate settings."""
1378 """Custom unittest test runner that uses appropriate settings."""
1378
1379
1379 def __init__(self, runner, *args, **kwargs):
1380 def __init__(self, runner, *args, **kwargs):
1380 super(TextTestRunner, self).__init__(*args, **kwargs)
1381 super(TextTestRunner, self).__init__(*args, **kwargs)
1381
1382
1382 self._runner = runner
1383 self._runner = runner
1383
1384
1384 def run(self, test):
1385 def run(self, test):
1385 result = TestResult(self._runner.options, self.stream,
1386 result = TestResult(self._runner.options, self.stream,
1386 self.descriptions, self.verbosity)
1387 self.descriptions, self.verbosity)
1387
1388
1388 test(result)
1389 test(result)
1389
1390
1390 failed = len(result.failures)
1391 failed = len(result.failures)
1391 warned = len(result.warned)
1392 warned = len(result.warned)
1392 skipped = len(result.skipped)
1393 skipped = len(result.skipped)
1393 ignored = len(result.ignored)
1394 ignored = len(result.ignored)
1394
1395
1395 iolock.acquire()
1396 iolock.acquire()
1396 self.stream.writeln('')
1397 self.stream.writeln('')
1397
1398
1398 if not self._runner.options.noskips:
1399 if not self._runner.options.noskips:
1399 for test, msg in result.skipped:
1400 for test, msg in result.skipped:
1400 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1401 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1401 for test, msg in result.warned:
1402 for test, msg in result.warned:
1402 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1403 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1403 for test, msg in result.failures:
1404 for test, msg in result.failures:
1404 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1405 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1405 for test, msg in result.errors:
1406 for test, msg in result.errors:
1406 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1407 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1407
1408
1408 if self._runner.options.xunit:
1409 if self._runner.options.xunit:
1409 xuf = open(self._runner.options.xunit, 'wb')
1410 xuf = open(self._runner.options.xunit, 'wb')
1410 try:
1411 try:
1411 timesd = dict(
1412 timesd = dict(
1412 (test, real) for test, cuser, csys, real in result.times)
1413 (test, real) for test, cuser, csys, real in result.times)
1413 doc = minidom.Document()
1414 doc = minidom.Document()
1414 s = doc.createElement('testsuite')
1415 s = doc.createElement('testsuite')
1415 s.setAttribute('name', 'run-tests')
1416 s.setAttribute('name', 'run-tests')
1416 s.setAttribute('tests', str(result.testsRun))
1417 s.setAttribute('tests', str(result.testsRun))
1417 s.setAttribute('errors', "0") # TODO
1418 s.setAttribute('errors', "0") # TODO
1418 s.setAttribute('failures', str(failed))
1419 s.setAttribute('failures', str(failed))
1419 s.setAttribute('skipped', str(skipped + ignored))
1420 s.setAttribute('skipped', str(skipped + ignored))
1420 doc.appendChild(s)
1421 doc.appendChild(s)
1421 for tc in result.successes:
1422 for tc in result.successes:
1422 t = doc.createElement('testcase')
1423 t = doc.createElement('testcase')
1423 t.setAttribute('name', tc.name)
1424 t.setAttribute('name', tc.name)
1424 t.setAttribute('time', '%.3f' % timesd[tc.name])
1425 t.setAttribute('time', '%.3f' % timesd[tc.name])
1425 s.appendChild(t)
1426 s.appendChild(t)
1426 for tc, err in sorted(result.faildata.iteritems()):
1427 for tc, err in sorted(result.faildata.iteritems()):
1427 t = doc.createElement('testcase')
1428 t = doc.createElement('testcase')
1428 t.setAttribute('name', tc)
1429 t.setAttribute('name', tc)
1429 t.setAttribute('time', '%.3f' % timesd[tc])
1430 t.setAttribute('time', '%.3f' % timesd[tc])
1430 cd = doc.createCDATASection(cdatasafe(err))
1431 cd = doc.createCDATASection(cdatasafe(err))
1431 t.appendChild(cd)
1432 t.appendChild(cd)
1432 s.appendChild(t)
1433 s.appendChild(t)
1433 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1434 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1434 finally:
1435 finally:
1435 xuf.close()
1436 xuf.close()
1436
1437
1437 if self._runner.options.json:
1438 if self._runner.options.json:
1438 if json is None:
1439 if json is None:
1439 raise ImportError("json module not installed")
1440 raise ImportError("json module not installed")
1440 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1441 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1441 fp = open(jsonpath, 'w')
1442 fp = open(jsonpath, 'w')
1442 try:
1443 try:
1443 timesd = {}
1444 timesd = {}
1444 for test, cuser, csys, real in result.times:
1445 for test, cuser, csys, real in result.times:
1445 timesd[test] = (real, cuser, csys)
1446 timesd[test] = (real, cuser, csys)
1446
1447
1447 outcome = {}
1448 outcome = {}
1448 for tc in result.successes:
1449 for tc in result.successes:
1449 testresult = {'result': 'success',
1450 testresult = {'result': 'success',
1450 'time': ('%0.3f' % timesd[tc.name][0]),
1451 'time': ('%0.3f' % timesd[tc.name][0]),
1451 'cuser': ('%0.3f' % timesd[tc.name][1]),
1452 'cuser': ('%0.3f' % timesd[tc.name][1]),
1452 'csys': ('%0.3f' % timesd[tc.name][2])}
1453 'csys': ('%0.3f' % timesd[tc.name][2])}
1453 outcome[tc.name] = testresult
1454 outcome[tc.name] = testresult
1454
1455
1455 for tc, err in sorted(result.faildata.iteritems()):
1456 for tc, err in sorted(result.faildata.iteritems()):
1456 testresult = {'result': 'failure',
1457 testresult = {'result': 'failure',
1457 'time': ('%0.3f' % timesd[tc][0]),
1458 'time': ('%0.3f' % timesd[tc][0]),
1458 'cuser': ('%0.3f' % timesd[tc][1]),
1459 'cuser': ('%0.3f' % timesd[tc][1]),
1459 'csys': ('%0.3f' % timesd[tc][2])}
1460 'csys': ('%0.3f' % timesd[tc][2])}
1460 outcome[tc] = testresult
1461 outcome[tc] = testresult
1461
1462
1462 for tc, reason in result.skipped:
1463 for tc, reason in result.skipped:
1463 testresult = {'result': 'skip',
1464 testresult = {'result': 'skip',
1464 'time': ('%0.3f' % timesd[tc.name][0]),
1465 'time': ('%0.3f' % timesd[tc.name][0]),
1465 'cuser': ('%0.3f' % timesd[tc.name][1]),
1466 'cuser': ('%0.3f' % timesd[tc.name][1]),
1466 'csys': ('%0.3f' % timesd[tc.name][2])}
1467 'csys': ('%0.3f' % timesd[tc.name][2])}
1467 outcome[tc.name] = testresult
1468 outcome[tc.name] = testresult
1468
1469
1469 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1470 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1470 fp.writelines(("testreport =", jsonout))
1471 fp.writelines(("testreport =", jsonout))
1471 finally:
1472 finally:
1472 fp.close()
1473 fp.close()
1473
1474
1474 self._runner._checkhglib('Tested')
1475 self._runner._checkhglib('Tested')
1475
1476
1476 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1477 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1477 % (result.testsRun,
1478 % (result.testsRun,
1478 skipped + ignored, warned, failed))
1479 skipped + ignored, warned, failed))
1479 if failed:
1480 if failed:
1480 self.stream.writeln('python hash seed: %s' %
1481 self.stream.writeln('python hash seed: %s' %
1481 os.environ['PYTHONHASHSEED'])
1482 os.environ['PYTHONHASHSEED'])
1482 if self._runner.options.time:
1483 if self._runner.options.time:
1483 self.printtimes(result.times)
1484 self.printtimes(result.times)
1484
1485
1485 iolock.release()
1486 iolock.release()
1486
1487
1487 return result
1488 return result
1488
1489
1489 def printtimes(self, times):
1490 def printtimes(self, times):
1490 # iolock held by run
1491 # iolock held by run
1491 self.stream.writeln('# Producing time report')
1492 self.stream.writeln('# Producing time report')
1492 times.sort(key=lambda t: (t[3]))
1493 times.sort(key=lambda t: (t[3]))
1493 cols = '%7.3f %7.3f %7.3f %s'
1494 cols = '%7.3f %7.3f %7.3f %s'
1494 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1495 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1495 'Test'))
1496 'Test'))
1496 for test, cuser, csys, real in times:
1497 for test, cuser, csys, real in times:
1497 self.stream.writeln(cols % (cuser, csys, real, test))
1498 self.stream.writeln(cols % (cuser, csys, real, test))
1498
1499
1499 class TestRunner(object):
1500 class TestRunner(object):
1500 """Holds context for executing tests.
1501 """Holds context for executing tests.
1501
1502
1502 Tests rely on a lot of state. This object holds it for them.
1503 Tests rely on a lot of state. This object holds it for them.
1503 """
1504 """
1504
1505
1505 # Programs required to run tests.
1506 # Programs required to run tests.
1506 REQUIREDTOOLS = [
1507 REQUIREDTOOLS = [
1507 os.path.basename(sys.executable),
1508 os.path.basename(sys.executable),
1508 'diff',
1509 'diff',
1509 'grep',
1510 'grep',
1510 'unzip',
1511 'unzip',
1511 'gunzip',
1512 'gunzip',
1512 'bunzip2',
1513 'bunzip2',
1513 'sed',
1514 'sed',
1514 ]
1515 ]
1515
1516
1516 # Maps file extensions to test class.
1517 # Maps file extensions to test class.
1517 TESTTYPES = [
1518 TESTTYPES = [
1518 ('.py', PythonTest),
1519 ('.py', PythonTest),
1519 ('.t', TTest),
1520 ('.t', TTest),
1520 ]
1521 ]
1521
1522
1522 def __init__(self):
1523 def __init__(self):
1523 self.options = None
1524 self.options = None
1524 self._testdir = None
1525 self._testdir = None
1525 self._hgtmp = None
1526 self._hgtmp = None
1526 self._installdir = None
1527 self._installdir = None
1527 self._bindir = None
1528 self._bindir = None
1528 self._tmpbinddir = None
1529 self._tmpbinddir = None
1529 self._pythondir = None
1530 self._pythondir = None
1530 self._coveragefile = None
1531 self._coveragefile = None
1531 self._createdfiles = []
1532 self._createdfiles = []
1532 self._hgpath = None
1533 self._hgpath = None
1533
1534
1534 def run(self, args, parser=None):
1535 def run(self, args, parser=None):
1535 """Run the test suite."""
1536 """Run the test suite."""
1536 oldmask = os.umask(022)
1537 oldmask = os.umask(022)
1537 try:
1538 try:
1538 parser = parser or getparser()
1539 parser = parser or getparser()
1539 options, args = parseargs(args, parser)
1540 options, args = parseargs(args, parser)
1540 self.options = options
1541 self.options = options
1541
1542
1542 self._checktools()
1543 self._checktools()
1543 tests = self.findtests(args)
1544 tests = self.findtests(args)
1544 return self._run(tests)
1545 return self._run(tests)
1545 finally:
1546 finally:
1546 os.umask(oldmask)
1547 os.umask(oldmask)
1547
1548
1548 def _run(self, tests):
1549 def _run(self, tests):
1549 if self.options.random:
1550 if self.options.random:
1550 random.shuffle(tests)
1551 random.shuffle(tests)
1551 else:
1552 else:
1552 # keywords for slow tests
1553 # keywords for slow tests
1553 slow = 'svn gendoc check-code-hg'.split()
1554 slow = 'svn gendoc check-code-hg'.split()
1554 def sortkey(f):
1555 def sortkey(f):
1555 # run largest tests first, as they tend to take the longest
1556 # run largest tests first, as they tend to take the longest
1556 try:
1557 try:
1557 val = -os.stat(f).st_size
1558 val = -os.stat(f).st_size
1558 except OSError, e:
1559 except OSError, e:
1559 if e.errno != errno.ENOENT:
1560 if e.errno != errno.ENOENT:
1560 raise
1561 raise
1561 return -1e9 # file does not exist, tell early
1562 return -1e9 # file does not exist, tell early
1562 for kw in slow:
1563 for kw in slow:
1563 if kw in f:
1564 if kw in f:
1564 val *= 10
1565 val *= 10
1565 return val
1566 return val
1566 tests.sort(key=sortkey)
1567 tests.sort(key=sortkey)
1567
1568
1568 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1569 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1569
1570
1570 if 'PYTHONHASHSEED' not in os.environ:
1571 if 'PYTHONHASHSEED' not in os.environ:
1571 # use a random python hash seed all the time
1572 # use a random python hash seed all the time
1572 # we do the randomness ourself to know what seed is used
1573 # we do the randomness ourself to know what seed is used
1573 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1574 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1574
1575
1575 if self.options.tmpdir:
1576 if self.options.tmpdir:
1576 self.options.keep_tmpdir = True
1577 self.options.keep_tmpdir = True
1577 tmpdir = self.options.tmpdir
1578 tmpdir = self.options.tmpdir
1578 if os.path.exists(tmpdir):
1579 if os.path.exists(tmpdir):
1579 # Meaning of tmpdir has changed since 1.3: we used to create
1580 # Meaning of tmpdir has changed since 1.3: we used to create
1580 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1581 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1581 # tmpdir already exists.
1582 # tmpdir already exists.
1582 print "error: temp dir %r already exists" % tmpdir
1583 print "error: temp dir %r already exists" % tmpdir
1583 return 1
1584 return 1
1584
1585
1585 # Automatically removing tmpdir sounds convenient, but could
1586 # Automatically removing tmpdir sounds convenient, but could
1586 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1587 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1587 # or "--tmpdir=$HOME".
1588 # or "--tmpdir=$HOME".
1588 #vlog("# Removing temp dir", tmpdir)
1589 #vlog("# Removing temp dir", tmpdir)
1589 #shutil.rmtree(tmpdir)
1590 #shutil.rmtree(tmpdir)
1590 os.makedirs(tmpdir)
1591 os.makedirs(tmpdir)
1591 else:
1592 else:
1592 d = None
1593 d = None
1593 if os.name == 'nt':
1594 if os.name == 'nt':
1594 # without this, we get the default temp dir location, but
1595 # without this, we get the default temp dir location, but
1595 # in all lowercase, which causes troubles with paths (issue3490)
1596 # in all lowercase, which causes troubles with paths (issue3490)
1596 d = os.getenv('TMP')
1597 d = os.getenv('TMP')
1597 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1598 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1598 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1599 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1599
1600
1600 if self.options.with_hg:
1601 if self.options.with_hg:
1601 self._installdir = None
1602 self._installdir = None
1602 self._bindir = os.path.dirname(os.path.realpath(
1603 self._bindir = os.path.dirname(os.path.realpath(
1603 self.options.with_hg))
1604 self.options.with_hg))
1604 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1605 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1605 os.makedirs(self._tmpbindir)
1606 os.makedirs(self._tmpbindir)
1606
1607
1607 # This looks redundant with how Python initializes sys.path from
1608 # This looks redundant with how Python initializes sys.path from
1608 # the location of the script being executed. Needed because the
1609 # the location of the script being executed. Needed because the
1609 # "hg" specified by --with-hg is not the only Python script
1610 # "hg" specified by --with-hg is not the only Python script
1610 # executed in the test suite that needs to import 'mercurial'
1611 # executed in the test suite that needs to import 'mercurial'
1611 # ... which means it's not really redundant at all.
1612 # ... which means it's not really redundant at all.
1612 self._pythondir = self._bindir
1613 self._pythondir = self._bindir
1613 else:
1614 else:
1614 self._installdir = os.path.join(self._hgtmp, "install")
1615 self._installdir = os.path.join(self._hgtmp, "install")
1615 self._bindir = os.environ["BINDIR"] = \
1616 self._bindir = os.environ["BINDIR"] = \
1616 os.path.join(self._installdir, "bin")
1617 os.path.join(self._installdir, "bin")
1617 self._tmpbindir = self._bindir
1618 self._tmpbindir = self._bindir
1618 self._pythondir = os.path.join(self._installdir, "lib", "python")
1619 self._pythondir = os.path.join(self._installdir, "lib", "python")
1619
1620
1620 os.environ["BINDIR"] = self._bindir
1621 os.environ["BINDIR"] = self._bindir
1621 os.environ["PYTHON"] = PYTHON
1622 os.environ["PYTHON"] = PYTHON
1622
1623
1623 path = [self._bindir] + os.environ["PATH"].split(os.pathsep)
1624 path = [self._bindir] + os.environ["PATH"].split(os.pathsep)
1624 if self._tmpbindir != self._bindir:
1625 if self._tmpbindir != self._bindir:
1625 path = [self._tmpbindir] + path
1626 path = [self._tmpbindir] + path
1626 os.environ["PATH"] = os.pathsep.join(path)
1627 os.environ["PATH"] = os.pathsep.join(path)
1627
1628
1628 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1629 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1629 # can run .../tests/run-tests.py test-foo where test-foo
1630 # can run .../tests/run-tests.py test-foo where test-foo
1630 # adds an extension to HGRC. Also include run-test.py directory to
1631 # adds an extension to HGRC. Also include run-test.py directory to
1631 # import modules like heredoctest.
1632 # import modules like heredoctest.
1632 pypath = [self._pythondir, self._testdir,
1633 pypath = [self._pythondir, self._testdir,
1633 os.path.abspath(os.path.dirname(__file__))]
1634 os.path.abspath(os.path.dirname(__file__))]
1634 # We have to augment PYTHONPATH, rather than simply replacing
1635 # We have to augment PYTHONPATH, rather than simply replacing
1635 # it, in case external libraries are only available via current
1636 # it, in case external libraries are only available via current
1636 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1637 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1637 # are in /opt/subversion.)
1638 # are in /opt/subversion.)
1638 oldpypath = os.environ.get(IMPL_PATH)
1639 oldpypath = os.environ.get(IMPL_PATH)
1639 if oldpypath:
1640 if oldpypath:
1640 pypath.append(oldpypath)
1641 pypath.append(oldpypath)
1641 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1642 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1642
1643
1643 self._coveragefile = os.path.join(self._testdir, '.coverage')
1644 self._coveragefile = os.path.join(self._testdir, '.coverage')
1644
1645
1645 vlog("# Using TESTDIR", self._testdir)
1646 vlog("# Using TESTDIR", self._testdir)
1646 vlog("# Using HGTMP", self._hgtmp)
1647 vlog("# Using HGTMP", self._hgtmp)
1647 vlog("# Using PATH", os.environ["PATH"])
1648 vlog("# Using PATH", os.environ["PATH"])
1648 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1649 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1649
1650
1650 try:
1651 try:
1651 return self._runtests(tests) or 0
1652 return self._runtests(tests) or 0
1652 finally:
1653 finally:
1653 time.sleep(.1)
1654 time.sleep(.1)
1654 self._cleanup()
1655 self._cleanup()
1655
1656
1656 def findtests(self, args):
1657 def findtests(self, args):
1657 """Finds possible test files from arguments.
1658 """Finds possible test files from arguments.
1658
1659
1659 If you wish to inject custom tests into the test harness, this would
1660 If you wish to inject custom tests into the test harness, this would
1660 be a good function to monkeypatch or override in a derived class.
1661 be a good function to monkeypatch or override in a derived class.
1661 """
1662 """
1662 if not args:
1663 if not args:
1663 if self.options.changed:
1664 if self.options.changed:
1664 proc = Popen4('hg st --rev "%s" -man0 .' %
1665 proc = Popen4('hg st --rev "%s" -man0 .' %
1665 self.options.changed, None, 0)
1666 self.options.changed, None, 0)
1666 stdout, stderr = proc.communicate()
1667 stdout, stderr = proc.communicate()
1667 args = stdout.strip('\0').split('\0')
1668 args = stdout.strip('\0').split('\0')
1668 else:
1669 else:
1669 args = os.listdir('.')
1670 args = os.listdir('.')
1670
1671
1671 return [t for t in args
1672 return [t for t in args
1672 if os.path.basename(t).startswith('test-')
1673 if os.path.basename(t).startswith('test-')
1673 and (t.endswith('.py') or t.endswith('.t'))]
1674 and (t.endswith('.py') or t.endswith('.t'))]
1674
1675
1675 def _runtests(self, tests):
1676 def _runtests(self, tests):
1676 try:
1677 try:
1677 if self._installdir:
1678 if self._installdir:
1678 self._installhg()
1679 self._installhg()
1679 self._checkhglib("Testing")
1680 self._checkhglib("Testing")
1680 else:
1681 else:
1681 self._usecorrectpython()
1682 self._usecorrectpython()
1682
1683
1683 if self.options.restart:
1684 if self.options.restart:
1684 orig = list(tests)
1685 orig = list(tests)
1685 while tests:
1686 while tests:
1686 if os.path.exists(tests[0] + ".err"):
1687 if os.path.exists(tests[0] + ".err"):
1687 break
1688 break
1688 tests.pop(0)
1689 tests.pop(0)
1689 if not tests:
1690 if not tests:
1690 print "running all tests"
1691 print "running all tests"
1691 tests = orig
1692 tests = orig
1692
1693
1693 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1694 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1694
1695
1695 failed = False
1696 failed = False
1696 warned = False
1697 warned = False
1697
1698
1698 suite = TestSuite(self._testdir,
1699 suite = TestSuite(self._testdir,
1699 jobs=self.options.jobs,
1700 jobs=self.options.jobs,
1700 whitelist=self.options.whitelisted,
1701 whitelist=self.options.whitelisted,
1701 blacklist=self.options.blacklist,
1702 blacklist=self.options.blacklist,
1702 retest=self.options.retest,
1703 retest=self.options.retest,
1703 keywords=self.options.keywords,
1704 keywords=self.options.keywords,
1704 loop=self.options.loop,
1705 loop=self.options.loop,
1705 tests=tests)
1706 tests=tests)
1706 verbosity = 1
1707 verbosity = 1
1707 if self.options.verbose:
1708 if self.options.verbose:
1708 verbosity = 2
1709 verbosity = 2
1709 runner = TextTestRunner(self, verbosity=verbosity)
1710 runner = TextTestRunner(self, verbosity=verbosity)
1710 result = runner.run(suite)
1711 result = runner.run(suite)
1711
1712
1712 if result.failures:
1713 if result.failures:
1713 failed = True
1714 failed = True
1714 if result.warned:
1715 if result.warned:
1715 warned = True
1716 warned = True
1716
1717
1717 if self.options.anycoverage:
1718 if self.options.anycoverage:
1718 self._outputcoverage()
1719 self._outputcoverage()
1719 except KeyboardInterrupt:
1720 except KeyboardInterrupt:
1720 failed = True
1721 failed = True
1721 print "\ninterrupted!"
1722 print "\ninterrupted!"
1722
1723
1723 if failed:
1724 if failed:
1724 return 1
1725 return 1
1725 if warned:
1726 if warned:
1726 return 80
1727 return 80
1727
1728
1728 def _gettest(self, test, count):
1729 def _gettest(self, test, count):
1729 """Obtain a Test by looking at its filename.
1730 """Obtain a Test by looking at its filename.
1730
1731
1731 Returns a Test instance. The Test may not be runnable if it doesn't
1732 Returns a Test instance. The Test may not be runnable if it doesn't
1732 map to a known type.
1733 map to a known type.
1733 """
1734 """
1734 lctest = test.lower()
1735 lctest = test.lower()
1735 testcls = Test
1736 testcls = Test
1736
1737
1737 for ext, cls in self.TESTTYPES:
1738 for ext, cls in self.TESTTYPES:
1738 if lctest.endswith(ext):
1739 if lctest.endswith(ext):
1739 testcls = cls
1740 testcls = cls
1740 break
1741 break
1741
1742
1742 refpath = os.path.join(self._testdir, test)
1743 refpath = os.path.join(self._testdir, test)
1743 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1744 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1744
1745
1745 return testcls(refpath, tmpdir,
1746 return testcls(refpath, tmpdir,
1746 keeptmpdir=self.options.keep_tmpdir,
1747 keeptmpdir=self.options.keep_tmpdir,
1747 debug=self.options.debug,
1748 debug=self.options.debug,
1748 timeout=self.options.timeout,
1749 timeout=self.options.timeout,
1749 startport=self.options.port + count * 3,
1750 startport=self.options.port + count * 3,
1750 extraconfigopts=self.options.extra_config_opt,
1751 extraconfigopts=self.options.extra_config_opt,
1751 py3kwarnings=self.options.py3k_warnings,
1752 py3kwarnings=self.options.py3k_warnings,
1752 shell=self.options.shell)
1753 shell=self.options.shell)
1753
1754
1754 def _cleanup(self):
1755 def _cleanup(self):
1755 """Clean up state from this test invocation."""
1756 """Clean up state from this test invocation."""
1756
1757
1757 if self.options.keep_tmpdir:
1758 if self.options.keep_tmpdir:
1758 return
1759 return
1759
1760
1760 vlog("# Cleaning up HGTMP", self._hgtmp)
1761 vlog("# Cleaning up HGTMP", self._hgtmp)
1761 shutil.rmtree(self._hgtmp, True)
1762 shutil.rmtree(self._hgtmp, True)
1762 for f in self._createdfiles:
1763 for f in self._createdfiles:
1763 try:
1764 try:
1764 os.remove(f)
1765 os.remove(f)
1765 except OSError:
1766 except OSError:
1766 pass
1767 pass
1767
1768
1768 def _usecorrectpython(self):
1769 def _usecorrectpython(self):
1769 """Configure the environment to use the appropriate Python in tests."""
1770 """Configure the environment to use the appropriate Python in tests."""
1770 # Tests must use the same interpreter as us or bad things will happen.
1771 # Tests must use the same interpreter as us or bad things will happen.
1771 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1772 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1772 if getattr(os, 'symlink', None):
1773 if getattr(os, 'symlink', None):
1773 vlog("# Making python executable in test path a symlink to '%s'" %
1774 vlog("# Making python executable in test path a symlink to '%s'" %
1774 sys.executable)
1775 sys.executable)
1775 mypython = os.path.join(self._tmpbindir, pyexename)
1776 mypython = os.path.join(self._tmpbindir, pyexename)
1776 try:
1777 try:
1777 if os.readlink(mypython) == sys.executable:
1778 if os.readlink(mypython) == sys.executable:
1778 return
1779 return
1779 os.unlink(mypython)
1780 os.unlink(mypython)
1780 except OSError, err:
1781 except OSError, err:
1781 if err.errno != errno.ENOENT:
1782 if err.errno != errno.ENOENT:
1782 raise
1783 raise
1783 if self._findprogram(pyexename) != sys.executable:
1784 if self._findprogram(pyexename) != sys.executable:
1784 try:
1785 try:
1785 os.symlink(sys.executable, mypython)
1786 os.symlink(sys.executable, mypython)
1786 self._createdfiles.append(mypython)
1787 self._createdfiles.append(mypython)
1787 except OSError, err:
1788 except OSError, err:
1788 # child processes may race, which is harmless
1789 # child processes may race, which is harmless
1789 if err.errno != errno.EEXIST:
1790 if err.errno != errno.EEXIST:
1790 raise
1791 raise
1791 else:
1792 else:
1792 exedir, exename = os.path.split(sys.executable)
1793 exedir, exename = os.path.split(sys.executable)
1793 vlog("# Modifying search path to find %s as %s in '%s'" %
1794 vlog("# Modifying search path to find %s as %s in '%s'" %
1794 (exename, pyexename, exedir))
1795 (exename, pyexename, exedir))
1795 path = os.environ['PATH'].split(os.pathsep)
1796 path = os.environ['PATH'].split(os.pathsep)
1796 while exedir in path:
1797 while exedir in path:
1797 path.remove(exedir)
1798 path.remove(exedir)
1798 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1799 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1799 if not self._findprogram(pyexename):
1800 if not self._findprogram(pyexename):
1800 print "WARNING: Cannot find %s in search path" % pyexename
1801 print "WARNING: Cannot find %s in search path" % pyexename
1801
1802
1802 def _installhg(self):
1803 def _installhg(self):
1803 """Install hg into the test environment.
1804 """Install hg into the test environment.
1804
1805
1805 This will also configure hg with the appropriate testing settings.
1806 This will also configure hg with the appropriate testing settings.
1806 """
1807 """
1807 vlog("# Performing temporary installation of HG")
1808 vlog("# Performing temporary installation of HG")
1808 installerrs = os.path.join("tests", "install.err")
1809 installerrs = os.path.join("tests", "install.err")
1809 compiler = ''
1810 compiler = ''
1810 if self.options.compiler:
1811 if self.options.compiler:
1811 compiler = '--compiler ' + self.options.compiler
1812 compiler = '--compiler ' + self.options.compiler
1812 pure = self.options.pure and "--pure" or ""
1813 pure = self.options.pure and "--pure" or ""
1813 py3 = ''
1814 py3 = ''
1814 if sys.version_info[0] == 3:
1815 if sys.version_info[0] == 3:
1815 py3 = '--c2to3'
1816 py3 = '--c2to3'
1816
1817
1817 # Run installer in hg root
1818 # Run installer in hg root
1818 script = os.path.realpath(sys.argv[0])
1819 script = os.path.realpath(sys.argv[0])
1819 hgroot = os.path.dirname(os.path.dirname(script))
1820 hgroot = os.path.dirname(os.path.dirname(script))
1820 os.chdir(hgroot)
1821 os.chdir(hgroot)
1821 nohome = '--home=""'
1822 nohome = '--home=""'
1822 if os.name == 'nt':
1823 if os.name == 'nt':
1823 # The --home="" trick works only on OS where os.sep == '/'
1824 # The --home="" trick works only on OS where os.sep == '/'
1824 # because of a distutils convert_path() fast-path. Avoid it at
1825 # because of a distutils convert_path() fast-path. Avoid it at
1825 # least on Windows for now, deal with .pydistutils.cfg bugs
1826 # least on Windows for now, deal with .pydistutils.cfg bugs
1826 # when they happen.
1827 # when they happen.
1827 nohome = ''
1828 nohome = ''
1828 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1829 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1829 ' build %(compiler)s --build-base="%(base)s"'
1830 ' build %(compiler)s --build-base="%(base)s"'
1830 ' install --force --prefix="%(prefix)s"'
1831 ' install --force --prefix="%(prefix)s"'
1831 ' --install-lib="%(libdir)s"'
1832 ' --install-lib="%(libdir)s"'
1832 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1833 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1833 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1834 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1834 'compiler': compiler,
1835 'compiler': compiler,
1835 'base': os.path.join(self._hgtmp, "build"),
1836 'base': os.path.join(self._hgtmp, "build"),
1836 'prefix': self._installdir, 'libdir': self._pythondir,
1837 'prefix': self._installdir, 'libdir': self._pythondir,
1837 'bindir': self._bindir,
1838 'bindir': self._bindir,
1838 'nohome': nohome, 'logfile': installerrs})
1839 'nohome': nohome, 'logfile': installerrs})
1839 vlog("# Running", cmd)
1840 vlog("# Running", cmd)
1840 if os.system(cmd) == 0:
1841 if os.system(cmd) == 0:
1841 if not self.options.verbose:
1842 if not self.options.verbose:
1842 os.remove(installerrs)
1843 os.remove(installerrs)
1843 else:
1844 else:
1844 f = open(installerrs, 'rb')
1845 f = open(installerrs, 'rb')
1845 for line in f:
1846 for line in f:
1846 print line
1847 print line
1847 f.close()
1848 f.close()
1848 sys.exit(1)
1849 sys.exit(1)
1849 os.chdir(self._testdir)
1850 os.chdir(self._testdir)
1850
1851
1851 self._usecorrectpython()
1852 self._usecorrectpython()
1852
1853
1853 if self.options.py3k_warnings and not self.options.anycoverage:
1854 if self.options.py3k_warnings and not self.options.anycoverage:
1854 vlog("# Updating hg command to enable Py3k Warnings switch")
1855 vlog("# Updating hg command to enable Py3k Warnings switch")
1855 f = open(os.path.join(self._bindir, 'hg'), 'rb')
1856 f = open(os.path.join(self._bindir, 'hg'), 'rb')
1856 lines = [line.rstrip() for line in f]
1857 lines = [line.rstrip() for line in f]
1857 lines[0] += ' -3'
1858 lines[0] += ' -3'
1858 f.close()
1859 f.close()
1859 f = open(os.path.join(self._bindir, 'hg'), 'wb')
1860 f = open(os.path.join(self._bindir, 'hg'), 'wb')
1860 for line in lines:
1861 for line in lines:
1861 f.write(line + '\n')
1862 f.write(line + '\n')
1862 f.close()
1863 f.close()
1863
1864
1864 hgbat = os.path.join(self._bindir, 'hg.bat')
1865 hgbat = os.path.join(self._bindir, 'hg.bat')
1865 if os.path.isfile(hgbat):
1866 if os.path.isfile(hgbat):
1866 # hg.bat expects to be put in bin/scripts while run-tests.py
1867 # hg.bat expects to be put in bin/scripts while run-tests.py
1867 # installation layout put it in bin/ directly. Fix it
1868 # installation layout put it in bin/ directly. Fix it
1868 f = open(hgbat, 'rb')
1869 f = open(hgbat, 'rb')
1869 data = f.read()
1870 data = f.read()
1870 f.close()
1871 f.close()
1871 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1872 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1872 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1873 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1873 '"%~dp0python" "%~dp0hg" %*')
1874 '"%~dp0python" "%~dp0hg" %*')
1874 f = open(hgbat, 'wb')
1875 f = open(hgbat, 'wb')
1875 f.write(data)
1876 f.write(data)
1876 f.close()
1877 f.close()
1877 else:
1878 else:
1878 print 'WARNING: cannot fix hg.bat reference to python.exe'
1879 print 'WARNING: cannot fix hg.bat reference to python.exe'
1879
1880
1880 if self.options.anycoverage:
1881 if self.options.anycoverage:
1881 custom = os.path.join(self._testdir, 'sitecustomize.py')
1882 custom = os.path.join(self._testdir, 'sitecustomize.py')
1882 target = os.path.join(self._pythondir, 'sitecustomize.py')
1883 target = os.path.join(self._pythondir, 'sitecustomize.py')
1883 vlog('# Installing coverage trigger to %s' % target)
1884 vlog('# Installing coverage trigger to %s' % target)
1884 shutil.copyfile(custom, target)
1885 shutil.copyfile(custom, target)
1885 rc = os.path.join(self._testdir, '.coveragerc')
1886 rc = os.path.join(self._testdir, '.coveragerc')
1886 vlog('# Installing coverage rc to %s' % rc)
1887 vlog('# Installing coverage rc to %s' % rc)
1887 os.environ['COVERAGE_PROCESS_START'] = rc
1888 os.environ['COVERAGE_PROCESS_START'] = rc
1888 fn = os.path.join(self._installdir, '..', '.coverage')
1889 fn = os.path.join(self._installdir, '..', '.coverage')
1889 os.environ['COVERAGE_FILE'] = fn
1890 os.environ['COVERAGE_FILE'] = fn
1890
1891
1891 def _checkhglib(self, verb):
1892 def _checkhglib(self, verb):
1892 """Ensure that the 'mercurial' package imported by python is
1893 """Ensure that the 'mercurial' package imported by python is
1893 the one we expect it to be. If not, print a warning to stderr."""
1894 the one we expect it to be. If not, print a warning to stderr."""
1894 if ((self._bindir == self._pythondir) and
1895 if ((self._bindir == self._pythondir) and
1895 (self._bindir != self._tmpbindir)):
1896 (self._bindir != self._tmpbindir)):
1896 # The pythondir has been infered from --with-hg flag.
1897 # The pythondir has been infered from --with-hg flag.
1897 # We cannot expect anything sensible here
1898 # We cannot expect anything sensible here
1898 return
1899 return
1899 expecthg = os.path.join(self._pythondir, 'mercurial')
1900 expecthg = os.path.join(self._pythondir, 'mercurial')
1900 actualhg = self._gethgpath()
1901 actualhg = self._gethgpath()
1901 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1902 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1902 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1903 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1903 ' (expected %s)\n'
1904 ' (expected %s)\n'
1904 % (verb, actualhg, expecthg))
1905 % (verb, actualhg, expecthg))
1905 def _gethgpath(self):
1906 def _gethgpath(self):
1906 """Return the path to the mercurial package that is actually found by
1907 """Return the path to the mercurial package that is actually found by
1907 the current Python interpreter."""
1908 the current Python interpreter."""
1908 if self._hgpath is not None:
1909 if self._hgpath is not None:
1909 return self._hgpath
1910 return self._hgpath
1910
1911
1911 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1912 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1912 pipe = os.popen(cmd % PYTHON)
1913 pipe = os.popen(cmd % PYTHON)
1913 try:
1914 try:
1914 self._hgpath = pipe.read().strip()
1915 self._hgpath = pipe.read().strip()
1915 finally:
1916 finally:
1916 pipe.close()
1917 pipe.close()
1917
1918
1918 return self._hgpath
1919 return self._hgpath
1919
1920
1920 def _outputcoverage(self):
1921 def _outputcoverage(self):
1921 """Produce code coverage output."""
1922 """Produce code coverage output."""
1922 vlog('# Producing coverage report')
1923 vlog('# Producing coverage report')
1923 os.chdir(self._pythondir)
1924 os.chdir(self._pythondir)
1924
1925
1925 def covrun(*args):
1926 def covrun(*args):
1926 cmd = 'coverage %s' % ' '.join(args)
1927 cmd = 'coverage %s' % ' '.join(args)
1927 vlog('# Running: %s' % cmd)
1928 vlog('# Running: %s' % cmd)
1928 os.system(cmd)
1929 os.system(cmd)
1929
1930
1930 covrun('-c')
1931 covrun('-c')
1931 omit = ','.join(os.path.join(x, '*') for x in
1932 omit = ','.join(os.path.join(x, '*') for x in
1932 [self._bindir, self._testdir])
1933 [self._bindir, self._testdir])
1933 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1934 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1934 if self.options.htmlcov:
1935 if self.options.htmlcov:
1935 htmldir = os.path.join(self._testdir, 'htmlcov')
1936 htmldir = os.path.join(self._testdir, 'htmlcov')
1936 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1937 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1937 '"--omit=%s"' % omit)
1938 '"--omit=%s"' % omit)
1938 if self.options.annotate:
1939 if self.options.annotate:
1939 adir = os.path.join(self._testdir, 'annotated')
1940 adir = os.path.join(self._testdir, 'annotated')
1940 if not os.path.isdir(adir):
1941 if not os.path.isdir(adir):
1941 os.mkdir(adir)
1942 os.mkdir(adir)
1942 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1943 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1943
1944
1944 def _findprogram(self, program):
1945 def _findprogram(self, program):
1945 """Search PATH for a executable program"""
1946 """Search PATH for a executable program"""
1946 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1947 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1947 name = os.path.join(p, program)
1948 name = os.path.join(p, program)
1948 if os.name == 'nt' or os.access(name, os.X_OK):
1949 if os.name == 'nt' or os.access(name, os.X_OK):
1949 return name
1950 return name
1950 return None
1951 return None
1951
1952
1952 def _checktools(self):
1953 def _checktools(self):
1953 """Ensure tools required to run tests are present."""
1954 """Ensure tools required to run tests are present."""
1954 for p in self.REQUIREDTOOLS:
1955 for p in self.REQUIREDTOOLS:
1955 if os.name == 'nt' and not p.endswith('.exe'):
1956 if os.name == 'nt' and not p.endswith('.exe'):
1956 p += '.exe'
1957 p += '.exe'
1957 found = self._findprogram(p)
1958 found = self._findprogram(p)
1958 if found:
1959 if found:
1959 vlog("# Found prerequisite", p, "at", found)
1960 vlog("# Found prerequisite", p, "at", found)
1960 else:
1961 else:
1961 print "WARNING: Did not find prerequisite tool: %s " % p
1962 print "WARNING: Did not find prerequisite tool: %s " % p
1962
1963
1963 if __name__ == '__main__':
1964 if __name__ == '__main__':
1964 runner = TestRunner()
1965 runner = TestRunner()
1965
1966
1966 try:
1967 try:
1967 import msvcrt
1968 import msvcrt
1968 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
1969 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
1969 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1970 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1970 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
1971 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
1971 except ImportError:
1972 except ImportError:
1972 pass
1973 pass
1973
1974
1974 sys.exit(runner.run(sys.argv[1:]))
1975 sys.exit(runner.run(sys.argv[1:]))
@@ -1,65 +1,66 b''
1 Create a repository:
1 Create a repository:
2
2
3 $ hg config
3 $ hg config
4 defaults.backout=-d "0 0"
4 defaults.backout=-d "0 0"
5 defaults.commit=-d "0 0"
5 defaults.commit=-d "0 0"
6 defaults.shelve=--date "0 0"
6 defaults.shelve=--date "0 0"
7 defaults.tag=-d "0 0"
7 defaults.tag=-d "0 0"
8 ui.slash=True
8 ui.slash=True
9 ui.interactive=False
9 ui.interactive=False
10 ui.mergemarkers=detailed
10 ui.mergemarkers=detailed
11 ui.promptecho=True
11 $ hg init t
12 $ hg init t
12 $ cd t
13 $ cd t
13
14
14 Make a changeset:
15 Make a changeset:
15
16
16 $ echo a > a
17 $ echo a > a
17 $ hg add a
18 $ hg add a
18 $ hg commit -m test
19 $ hg commit -m test
19
20
20 This command is ancient:
21 This command is ancient:
21
22
22 $ hg history
23 $ hg history
23 changeset: 0:acb14030fe0a
24 changeset: 0:acb14030fe0a
24 tag: tip
25 tag: tip
25 user: test
26 user: test
26 date: Thu Jan 01 00:00:00 1970 +0000
27 date: Thu Jan 01 00:00:00 1970 +0000
27 summary: test
28 summary: test
28
29
29
30
30 Verify that updating to revision 0 via commands.update() works properly
31 Verify that updating to revision 0 via commands.update() works properly
31
32
32 $ cat <<EOF > update_to_rev0.py
33 $ cat <<EOF > update_to_rev0.py
33 > from mercurial import ui, hg, commands
34 > from mercurial import ui, hg, commands
34 > myui = ui.ui()
35 > myui = ui.ui()
35 > repo = hg.repository(myui, path='.')
36 > repo = hg.repository(myui, path='.')
36 > commands.update(myui, repo, rev=0)
37 > commands.update(myui, repo, rev=0)
37 > EOF
38 > EOF
38 $ hg up null
39 $ hg up null
39 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
40 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
40 $ python ./update_to_rev0.py
41 $ python ./update_to_rev0.py
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 $ hg identify -n
43 $ hg identify -n
43 0
44 0
44
45
45
46
46 Poke around at hashes:
47 Poke around at hashes:
47
48
48 $ hg manifest --debug
49 $ hg manifest --debug
49 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
50 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
50
51
51 $ hg cat a
52 $ hg cat a
52 a
53 a
53
54
54 Verify should succeed:
55 Verify should succeed:
55
56
56 $ hg verify
57 $ hg verify
57 checking changesets
58 checking changesets
58 checking manifests
59 checking manifests
59 crosschecking files in changesets and manifests
60 crosschecking files in changesets and manifests
60 checking files
61 checking files
61 1 files, 1 changesets, 1 total revisions
62 1 files, 1 changesets, 1 total revisions
62
63
63 At the end...
64 At the end...
64
65
65 $ cd ..
66 $ cd ..
@@ -1,607 +1,612 b''
1 #if windows
1 #if windows
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 #else
3 #else
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 #endif
5 #endif
6 $ export PYTHONPATH
6 $ export PYTHONPATH
7
7
8 typical client does not want echo-back messages, so test without it:
9
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 $ mv $HGRCPATH.new $HGRCPATH
12
8 $ hg init repo
13 $ hg init repo
9 $ cd repo
14 $ cd repo
10
15
11 >>> from hgclient import readchannel, runcommand, check
16 >>> from hgclient import readchannel, runcommand, check
12 >>> @check
17 >>> @check
13 ... def hellomessage(server):
18 ... def hellomessage(server):
14 ... ch, data = readchannel(server)
19 ... ch, data = readchannel(server)
15 ... print '%c, %r' % (ch, data)
20 ... print '%c, %r' % (ch, data)
16 ... # run an arbitrary command to make sure the next thing the server
21 ... # run an arbitrary command to make sure the next thing the server
17 ... # sends isn't part of the hello message
22 ... # sends isn't part of the hello message
18 ... runcommand(server, ['id'])
23 ... runcommand(server, ['id'])
19 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
24 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
20 *** runcommand id
25 *** runcommand id
21 000000000000 tip
26 000000000000 tip
22
27
23 >>> from hgclient import check
28 >>> from hgclient import check
24 >>> @check
29 >>> @check
25 ... def unknowncommand(server):
30 ... def unknowncommand(server):
26 ... server.stdin.write('unknowncommand\n')
31 ... server.stdin.write('unknowncommand\n')
27 abort: unknown command unknowncommand
32 abort: unknown command unknowncommand
28
33
29 >>> from hgclient import readchannel, runcommand, check
34 >>> from hgclient import readchannel, runcommand, check
30 >>> @check
35 >>> @check
31 ... def checkruncommand(server):
36 ... def checkruncommand(server):
32 ... # hello block
37 ... # hello block
33 ... readchannel(server)
38 ... readchannel(server)
34 ...
39 ...
35 ... # no args
40 ... # no args
36 ... runcommand(server, [])
41 ... runcommand(server, [])
37 ...
42 ...
38 ... # global options
43 ... # global options
39 ... runcommand(server, ['id', '--quiet'])
44 ... runcommand(server, ['id', '--quiet'])
40 ...
45 ...
41 ... # make sure global options don't stick through requests
46 ... # make sure global options don't stick through requests
42 ... runcommand(server, ['id'])
47 ... runcommand(server, ['id'])
43 ...
48 ...
44 ... # --config
49 ... # --config
45 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
50 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
46 ...
51 ...
47 ... # make sure --config doesn't stick
52 ... # make sure --config doesn't stick
48 ... runcommand(server, ['id'])
53 ... runcommand(server, ['id'])
49 ...
54 ...
50 ... # negative return code should be masked
55 ... # negative return code should be masked
51 ... runcommand(server, ['id', '-runknown'])
56 ... runcommand(server, ['id', '-runknown'])
52 *** runcommand
57 *** runcommand
53 Mercurial Distributed SCM
58 Mercurial Distributed SCM
54
59
55 basic commands:
60 basic commands:
56
61
57 add add the specified files on the next commit
62 add add the specified files on the next commit
58 annotate show changeset information by line for each file
63 annotate show changeset information by line for each file
59 clone make a copy of an existing repository
64 clone make a copy of an existing repository
60 commit commit the specified files or all outstanding changes
65 commit commit the specified files or all outstanding changes
61 diff diff repository (or selected files)
66 diff diff repository (or selected files)
62 export dump the header and diffs for one or more changesets
67 export dump the header and diffs for one or more changesets
63 forget forget the specified files on the next commit
68 forget forget the specified files on the next commit
64 init create a new repository in the given directory
69 init create a new repository in the given directory
65 log show revision history of entire repository or files
70 log show revision history of entire repository or files
66 merge merge working directory with another revision
71 merge merge working directory with another revision
67 pull pull changes from the specified source
72 pull pull changes from the specified source
68 push push changes to the specified destination
73 push push changes to the specified destination
69 remove remove the specified files on the next commit
74 remove remove the specified files on the next commit
70 serve start stand-alone webserver
75 serve start stand-alone webserver
71 status show changed files in the working directory
76 status show changed files in the working directory
72 summary summarize working directory state
77 summary summarize working directory state
73 update update working directory (or switch revisions)
78 update update working directory (or switch revisions)
74
79
75 (use "hg help" for the full list of commands or "hg -v" for details)
80 (use "hg help" for the full list of commands or "hg -v" for details)
76 *** runcommand id --quiet
81 *** runcommand id --quiet
77 000000000000
82 000000000000
78 *** runcommand id
83 *** runcommand id
79 000000000000 tip
84 000000000000 tip
80 *** runcommand id --config ui.quiet=True
85 *** runcommand id --config ui.quiet=True
81 000000000000
86 000000000000
82 *** runcommand id
87 *** runcommand id
83 000000000000 tip
88 000000000000 tip
84 *** runcommand id -runknown
89 *** runcommand id -runknown
85 abort: unknown revision 'unknown'!
90 abort: unknown revision 'unknown'!
86 [255]
91 [255]
87
92
88 >>> from hgclient import readchannel, check
93 >>> from hgclient import readchannel, check
89 >>> @check
94 >>> @check
90 ... def inputeof(server):
95 ... def inputeof(server):
91 ... readchannel(server)
96 ... readchannel(server)
92 ... server.stdin.write('runcommand\n')
97 ... server.stdin.write('runcommand\n')
93 ... # close stdin while server is waiting for input
98 ... # close stdin while server is waiting for input
94 ... server.stdin.close()
99 ... server.stdin.close()
95 ...
100 ...
96 ... # server exits with 1 if the pipe closed while reading the command
101 ... # server exits with 1 if the pipe closed while reading the command
97 ... print 'server exit code =', server.wait()
102 ... print 'server exit code =', server.wait()
98 server exit code = 1
103 server exit code = 1
99
104
100 >>> import cStringIO
105 >>> import cStringIO
101 >>> from hgclient import readchannel, runcommand, check
106 >>> from hgclient import readchannel, runcommand, check
102 >>> @check
107 >>> @check
103 ... def serverinput(server):
108 ... def serverinput(server):
104 ... readchannel(server)
109 ... readchannel(server)
105 ...
110 ...
106 ... patch = """
111 ... patch = """
107 ... # HG changeset patch
112 ... # HG changeset patch
108 ... # User test
113 ... # User test
109 ... # Date 0 0
114 ... # Date 0 0
110 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
115 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
111 ... # Parent 0000000000000000000000000000000000000000
116 ... # Parent 0000000000000000000000000000000000000000
112 ... 1
117 ... 1
113 ...
118 ...
114 ... diff -r 000000000000 -r c103a3dec114 a
119 ... diff -r 000000000000 -r c103a3dec114 a
115 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
120 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
116 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
121 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
117 ... @@ -0,0 +1,1 @@
122 ... @@ -0,0 +1,1 @@
118 ... +1
123 ... +1
119 ... """
124 ... """
120 ...
125 ...
121 ... runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
126 ... runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
122 ... runcommand(server, ['log'])
127 ... runcommand(server, ['log'])
123 *** runcommand import -
128 *** runcommand import -
124 applying patch from stdin
129 applying patch from stdin
125 *** runcommand log
130 *** runcommand log
126 changeset: 0:eff892de26ec
131 changeset: 0:eff892de26ec
127 tag: tip
132 tag: tip
128 user: test
133 user: test
129 date: Thu Jan 01 00:00:00 1970 +0000
134 date: Thu Jan 01 00:00:00 1970 +0000
130 summary: 1
135 summary: 1
131
136
132
137
133 check that --cwd doesn't persist between requests:
138 check that --cwd doesn't persist between requests:
134
139
135 $ mkdir foo
140 $ mkdir foo
136 $ touch foo/bar
141 $ touch foo/bar
137 >>> from hgclient import readchannel, runcommand, check
142 >>> from hgclient import readchannel, runcommand, check
138 >>> @check
143 >>> @check
139 ... def cwd(server):
144 ... def cwd(server):
140 ... readchannel(server)
145 ... readchannel(server)
141 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
146 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
142 ... runcommand(server, ['st', 'foo/bar'])
147 ... runcommand(server, ['st', 'foo/bar'])
143 *** runcommand --cwd foo st bar
148 *** runcommand --cwd foo st bar
144 ? bar
149 ? bar
145 *** runcommand st foo/bar
150 *** runcommand st foo/bar
146 ? foo/bar
151 ? foo/bar
147
152
148 $ rm foo/bar
153 $ rm foo/bar
149
154
150
155
151 check that local configs for the cached repo aren't inherited when -R is used:
156 check that local configs for the cached repo aren't inherited when -R is used:
152
157
153 $ cat <<EOF >> .hg/hgrc
158 $ cat <<EOF >> .hg/hgrc
154 > [ui]
159 > [ui]
155 > foo = bar
160 > foo = bar
156 > EOF
161 > EOF
157
162
158 >>> from hgclient import readchannel, sep, runcommand, check
163 >>> from hgclient import readchannel, sep, runcommand, check
159 >>> @check
164 >>> @check
160 ... def localhgrc(server):
165 ... def localhgrc(server):
161 ... readchannel(server)
166 ... readchannel(server)
162 ...
167 ...
163 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
168 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
164 ... # show it
169 ... # show it
165 ... runcommand(server, ['showconfig'], outfilter=sep)
170 ... runcommand(server, ['showconfig'], outfilter=sep)
166 ...
171 ...
167 ... # but not for this repo
172 ... # but not for this repo
168 ... runcommand(server, ['init', 'foo'])
173 ... runcommand(server, ['init', 'foo'])
169 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
174 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
170 *** runcommand showconfig
175 *** runcommand showconfig
171 bundle.mainreporoot=$TESTTMP/repo
176 bundle.mainreporoot=$TESTTMP/repo
172 defaults.backout=-d "0 0"
177 defaults.backout=-d "0 0"
173 defaults.commit=-d "0 0"
178 defaults.commit=-d "0 0"
174 defaults.shelve=--date "0 0"
179 defaults.shelve=--date "0 0"
175 defaults.tag=-d "0 0"
180 defaults.tag=-d "0 0"
176 ui.slash=True
181 ui.slash=True
177 ui.interactive=False
182 ui.interactive=False
178 ui.mergemarkers=detailed
183 ui.mergemarkers=detailed
179 ui.foo=bar
184 ui.foo=bar
180 ui.nontty=true
185 ui.nontty=true
181 *** runcommand init foo
186 *** runcommand init foo
182 *** runcommand -R foo showconfig ui defaults
187 *** runcommand -R foo showconfig ui defaults
183 defaults.backout=-d "0 0"
188 defaults.backout=-d "0 0"
184 defaults.commit=-d "0 0"
189 defaults.commit=-d "0 0"
185 defaults.shelve=--date "0 0"
190 defaults.shelve=--date "0 0"
186 defaults.tag=-d "0 0"
191 defaults.tag=-d "0 0"
187 ui.slash=True
192 ui.slash=True
188 ui.interactive=False
193 ui.interactive=False
189 ui.mergemarkers=detailed
194 ui.mergemarkers=detailed
190 ui.nontty=true
195 ui.nontty=true
191
196
192 $ rm -R foo
197 $ rm -R foo
193
198
194 #if windows
199 #if windows
195 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
200 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
196 #else
201 #else
197 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
202 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
198 #endif
203 #endif
199
204
200 $ cat <<EOF > hook.py
205 $ cat <<EOF > hook.py
201 > import sys
206 > import sys
202 > def hook(**args):
207 > def hook(**args):
203 > print 'hook talking'
208 > print 'hook talking'
204 > print 'now try to read something: %r' % sys.stdin.read()
209 > print 'now try to read something: %r' % sys.stdin.read()
205 > EOF
210 > EOF
206
211
207 >>> import cStringIO
212 >>> import cStringIO
208 >>> from hgclient import readchannel, runcommand, check
213 >>> from hgclient import readchannel, runcommand, check
209 >>> @check
214 >>> @check
210 ... def hookoutput(server):
215 ... def hookoutput(server):
211 ... readchannel(server)
216 ... readchannel(server)
212 ... runcommand(server, ['--config',
217 ... runcommand(server, ['--config',
213 ... 'hooks.pre-identify=python:hook.hook',
218 ... 'hooks.pre-identify=python:hook.hook',
214 ... 'id'],
219 ... 'id'],
215 ... input=cStringIO.StringIO('some input'))
220 ... input=cStringIO.StringIO('some input'))
216 *** runcommand --config hooks.pre-identify=python:hook.hook id
221 *** runcommand --config hooks.pre-identify=python:hook.hook id
217 hook talking
222 hook talking
218 now try to read something: 'some input'
223 now try to read something: 'some input'
219 eff892de26ec tip
224 eff892de26ec tip
220
225
221 $ rm hook.py*
226 $ rm hook.py*
222
227
223 $ echo a >> a
228 $ echo a >> a
224 >>> import os
229 >>> import os
225 >>> from hgclient import readchannel, runcommand, check
230 >>> from hgclient import readchannel, runcommand, check
226 >>> @check
231 >>> @check
227 ... def outsidechanges(server):
232 ... def outsidechanges(server):
228 ... readchannel(server)
233 ... readchannel(server)
229 ... runcommand(server, ['status'])
234 ... runcommand(server, ['status'])
230 ... os.system('hg ci -Am2')
235 ... os.system('hg ci -Am2')
231 ... runcommand(server, ['tip'])
236 ... runcommand(server, ['tip'])
232 ... runcommand(server, ['status'])
237 ... runcommand(server, ['status'])
233 *** runcommand status
238 *** runcommand status
234 M a
239 M a
235 *** runcommand tip
240 *** runcommand tip
236 changeset: 1:d3a0a68be6de
241 changeset: 1:d3a0a68be6de
237 tag: tip
242 tag: tip
238 user: test
243 user: test
239 date: Thu Jan 01 00:00:00 1970 +0000
244 date: Thu Jan 01 00:00:00 1970 +0000
240 summary: 2
245 summary: 2
241
246
242 *** runcommand status
247 *** runcommand status
243
248
244 >>> import os
249 >>> import os
245 >>> from hgclient import readchannel, runcommand, check
250 >>> from hgclient import readchannel, runcommand, check
246 >>> @check
251 >>> @check
247 ... def bookmarks(server):
252 ... def bookmarks(server):
248 ... readchannel(server)
253 ... readchannel(server)
249 ... runcommand(server, ['bookmarks'])
254 ... runcommand(server, ['bookmarks'])
250 ...
255 ...
251 ... # changes .hg/bookmarks
256 ... # changes .hg/bookmarks
252 ... os.system('hg bookmark -i bm1')
257 ... os.system('hg bookmark -i bm1')
253 ... os.system('hg bookmark -i bm2')
258 ... os.system('hg bookmark -i bm2')
254 ... runcommand(server, ['bookmarks'])
259 ... runcommand(server, ['bookmarks'])
255 ...
260 ...
256 ... # changes .hg/bookmarks.current
261 ... # changes .hg/bookmarks.current
257 ... os.system('hg upd bm1 -q')
262 ... os.system('hg upd bm1 -q')
258 ... runcommand(server, ['bookmarks'])
263 ... runcommand(server, ['bookmarks'])
259 ...
264 ...
260 ... runcommand(server, ['bookmarks', 'bm3'])
265 ... runcommand(server, ['bookmarks', 'bm3'])
261 ... f = open('a', 'ab')
266 ... f = open('a', 'ab')
262 ... f.write('a\n')
267 ... f.write('a\n')
263 ... f.close()
268 ... f.close()
264 ... runcommand(server, ['commit', '-Amm'])
269 ... runcommand(server, ['commit', '-Amm'])
265 ... runcommand(server, ['bookmarks'])
270 ... runcommand(server, ['bookmarks'])
266 *** runcommand bookmarks
271 *** runcommand bookmarks
267 no bookmarks set
272 no bookmarks set
268 *** runcommand bookmarks
273 *** runcommand bookmarks
269 bm1 1:d3a0a68be6de
274 bm1 1:d3a0a68be6de
270 bm2 1:d3a0a68be6de
275 bm2 1:d3a0a68be6de
271 *** runcommand bookmarks
276 *** runcommand bookmarks
272 * bm1 1:d3a0a68be6de
277 * bm1 1:d3a0a68be6de
273 bm2 1:d3a0a68be6de
278 bm2 1:d3a0a68be6de
274 *** runcommand bookmarks bm3
279 *** runcommand bookmarks bm3
275 *** runcommand commit -Amm
280 *** runcommand commit -Amm
276 *** runcommand bookmarks
281 *** runcommand bookmarks
277 bm1 1:d3a0a68be6de
282 bm1 1:d3a0a68be6de
278 bm2 1:d3a0a68be6de
283 bm2 1:d3a0a68be6de
279 * bm3 2:aef17e88f5f0
284 * bm3 2:aef17e88f5f0
280
285
281 >>> import os
286 >>> import os
282 >>> from hgclient import readchannel, runcommand, check
287 >>> from hgclient import readchannel, runcommand, check
283 >>> @check
288 >>> @check
284 ... def tagscache(server):
289 ... def tagscache(server):
285 ... readchannel(server)
290 ... readchannel(server)
286 ... runcommand(server, ['id', '-t', '-r', '0'])
291 ... runcommand(server, ['id', '-t', '-r', '0'])
287 ... os.system('hg tag -r 0 foo')
292 ... os.system('hg tag -r 0 foo')
288 ... runcommand(server, ['id', '-t', '-r', '0'])
293 ... runcommand(server, ['id', '-t', '-r', '0'])
289 *** runcommand id -t -r 0
294 *** runcommand id -t -r 0
290
295
291 *** runcommand id -t -r 0
296 *** runcommand id -t -r 0
292 foo
297 foo
293
298
294 >>> import os
299 >>> import os
295 >>> from hgclient import readchannel, runcommand, check
300 >>> from hgclient import readchannel, runcommand, check
296 >>> @check
301 >>> @check
297 ... def setphase(server):
302 ... def setphase(server):
298 ... readchannel(server)
303 ... readchannel(server)
299 ... runcommand(server, ['phase', '-r', '.'])
304 ... runcommand(server, ['phase', '-r', '.'])
300 ... os.system('hg phase -r . -p')
305 ... os.system('hg phase -r . -p')
301 ... runcommand(server, ['phase', '-r', '.'])
306 ... runcommand(server, ['phase', '-r', '.'])
302 *** runcommand phase -r .
307 *** runcommand phase -r .
303 3: draft
308 3: draft
304 *** runcommand phase -r .
309 *** runcommand phase -r .
305 3: public
310 3: public
306
311
307 $ echo a >> a
312 $ echo a >> a
308 >>> from hgclient import readchannel, runcommand, check
313 >>> from hgclient import readchannel, runcommand, check
309 >>> @check
314 >>> @check
310 ... def rollback(server):
315 ... def rollback(server):
311 ... readchannel(server)
316 ... readchannel(server)
312 ... runcommand(server, ['phase', '-r', '.', '-p'])
317 ... runcommand(server, ['phase', '-r', '.', '-p'])
313 ... runcommand(server, ['commit', '-Am.'])
318 ... runcommand(server, ['commit', '-Am.'])
314 ... runcommand(server, ['rollback'])
319 ... runcommand(server, ['rollback'])
315 ... runcommand(server, ['phase', '-r', '.'])
320 ... runcommand(server, ['phase', '-r', '.'])
316 *** runcommand phase -r . -p
321 *** runcommand phase -r . -p
317 no phases changed
322 no phases changed
318 [1]
323 [1]
319 *** runcommand commit -Am.
324 *** runcommand commit -Am.
320 *** runcommand rollback
325 *** runcommand rollback
321 repository tip rolled back to revision 3 (undo commit)
326 repository tip rolled back to revision 3 (undo commit)
322 working directory now based on revision 3
327 working directory now based on revision 3
323 *** runcommand phase -r .
328 *** runcommand phase -r .
324 3: public
329 3: public
325
330
326 >>> import os
331 >>> import os
327 >>> from hgclient import readchannel, runcommand, check
332 >>> from hgclient import readchannel, runcommand, check
328 >>> @check
333 >>> @check
329 ... def branch(server):
334 ... def branch(server):
330 ... readchannel(server)
335 ... readchannel(server)
331 ... runcommand(server, ['branch'])
336 ... runcommand(server, ['branch'])
332 ... os.system('hg branch foo')
337 ... os.system('hg branch foo')
333 ... runcommand(server, ['branch'])
338 ... runcommand(server, ['branch'])
334 ... os.system('hg branch default')
339 ... os.system('hg branch default')
335 *** runcommand branch
340 *** runcommand branch
336 default
341 default
337 marked working directory as branch foo
342 marked working directory as branch foo
338 (branches are permanent and global, did you want a bookmark?)
343 (branches are permanent and global, did you want a bookmark?)
339 *** runcommand branch
344 *** runcommand branch
340 foo
345 foo
341 marked working directory as branch default
346 marked working directory as branch default
342 (branches are permanent and global, did you want a bookmark?)
347 (branches are permanent and global, did you want a bookmark?)
343
348
344 $ touch .hgignore
349 $ touch .hgignore
345 >>> import os
350 >>> import os
346 >>> from hgclient import readchannel, runcommand, check
351 >>> from hgclient import readchannel, runcommand, check
347 >>> @check
352 >>> @check
348 ... def hgignore(server):
353 ... def hgignore(server):
349 ... readchannel(server)
354 ... readchannel(server)
350 ... runcommand(server, ['commit', '-Am.'])
355 ... runcommand(server, ['commit', '-Am.'])
351 ... f = open('ignored-file', 'ab')
356 ... f = open('ignored-file', 'ab')
352 ... f.write('')
357 ... f.write('')
353 ... f.close()
358 ... f.close()
354 ... f = open('.hgignore', 'ab')
359 ... f = open('.hgignore', 'ab')
355 ... f.write('ignored-file')
360 ... f.write('ignored-file')
356 ... f.close()
361 ... f.close()
357 ... runcommand(server, ['status', '-i', '-u'])
362 ... runcommand(server, ['status', '-i', '-u'])
358 *** runcommand commit -Am.
363 *** runcommand commit -Am.
359 adding .hgignore
364 adding .hgignore
360 *** runcommand status -i -u
365 *** runcommand status -i -u
361 I ignored-file
366 I ignored-file
362
367
363 >>> import os
368 >>> import os
364 >>> from hgclient import readchannel, sep, runcommand, check
369 >>> from hgclient import readchannel, sep, runcommand, check
365 >>> @check
370 >>> @check
366 ... def phasecacheafterstrip(server):
371 ... def phasecacheafterstrip(server):
367 ... readchannel(server)
372 ... readchannel(server)
368 ...
373 ...
369 ... # create new head, 5:731265503d86
374 ... # create new head, 5:731265503d86
370 ... runcommand(server, ['update', '-C', '0'])
375 ... runcommand(server, ['update', '-C', '0'])
371 ... f = open('a', 'ab')
376 ... f = open('a', 'ab')
372 ... f.write('a\n')
377 ... f.write('a\n')
373 ... f.close()
378 ... f.close()
374 ... runcommand(server, ['commit', '-Am.', 'a'])
379 ... runcommand(server, ['commit', '-Am.', 'a'])
375 ... runcommand(server, ['log', '-Gq'])
380 ... runcommand(server, ['log', '-Gq'])
376 ...
381 ...
377 ... # make it public; draft marker moves to 4:7966c8e3734d
382 ... # make it public; draft marker moves to 4:7966c8e3734d
378 ... runcommand(server, ['phase', '-p', '.'])
383 ... runcommand(server, ['phase', '-p', '.'])
379 ... # load _phasecache.phaseroots
384 ... # load _phasecache.phaseroots
380 ... runcommand(server, ['phase', '.'], outfilter=sep)
385 ... runcommand(server, ['phase', '.'], outfilter=sep)
381 ...
386 ...
382 ... # strip 1::4 outside server
387 ... # strip 1::4 outside server
383 ... os.system('hg -q --config extensions.mq= strip 1')
388 ... os.system('hg -q --config extensions.mq= strip 1')
384 ...
389 ...
385 ... # shouldn't raise "7966c8e3734d: no node!"
390 ... # shouldn't raise "7966c8e3734d: no node!"
386 ... runcommand(server, ['branches'])
391 ... runcommand(server, ['branches'])
387 *** runcommand update -C 0
392 *** runcommand update -C 0
388 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
393 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
389 (leaving bookmark bm3)
394 (leaving bookmark bm3)
390 *** runcommand commit -Am. a
395 *** runcommand commit -Am. a
391 created new head
396 created new head
392 *** runcommand log -Gq
397 *** runcommand log -Gq
393 @ 5:731265503d86
398 @ 5:731265503d86
394 |
399 |
395 | o 4:7966c8e3734d
400 | o 4:7966c8e3734d
396 | |
401 | |
397 | o 3:b9b85890c400
402 | o 3:b9b85890c400
398 | |
403 | |
399 | o 2:aef17e88f5f0
404 | o 2:aef17e88f5f0
400 | |
405 | |
401 | o 1:d3a0a68be6de
406 | o 1:d3a0a68be6de
402 |/
407 |/
403 o 0:eff892de26ec
408 o 0:eff892de26ec
404
409
405 *** runcommand phase -p .
410 *** runcommand phase -p .
406 *** runcommand phase .
411 *** runcommand phase .
407 5: public
412 5: public
408 *** runcommand branches
413 *** runcommand branches
409 default 1:731265503d86
414 default 1:731265503d86
410
415
411 $ cat >> .hg/hgrc << EOF
416 $ cat >> .hg/hgrc << EOF
412 > [experimental]
417 > [experimental]
413 > evolution=createmarkers
418 > evolution=createmarkers
414 > EOF
419 > EOF
415
420
416 >>> import os
421 >>> import os
417 >>> from hgclient import readchannel, runcommand, check
422 >>> from hgclient import readchannel, runcommand, check
418 >>> @check
423 >>> @check
419 ... def obsolete(server):
424 ... def obsolete(server):
420 ... readchannel(server)
425 ... readchannel(server)
421 ...
426 ...
422 ... runcommand(server, ['up', 'null'])
427 ... runcommand(server, ['up', 'null'])
423 ... runcommand(server, ['phase', '-df', 'tip'])
428 ... runcommand(server, ['phase', '-df', 'tip'])
424 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
429 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
425 ... if os.name == 'nt':
430 ... if os.name == 'nt':
426 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
431 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
427 ... os.system(cmd)
432 ... os.system(cmd)
428 ... runcommand(server, ['log', '--hidden'])
433 ... runcommand(server, ['log', '--hidden'])
429 ... runcommand(server, ['log'])
434 ... runcommand(server, ['log'])
430 *** runcommand up null
435 *** runcommand up null
431 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
436 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
432 *** runcommand phase -df tip
437 *** runcommand phase -df tip
433 *** runcommand log --hidden
438 *** runcommand log --hidden
434 changeset: 1:731265503d86
439 changeset: 1:731265503d86
435 tag: tip
440 tag: tip
436 user: test
441 user: test
437 date: Thu Jan 01 00:00:00 1970 +0000
442 date: Thu Jan 01 00:00:00 1970 +0000
438 summary: .
443 summary: .
439
444
440 changeset: 0:eff892de26ec
445 changeset: 0:eff892de26ec
441 bookmark: bm1
446 bookmark: bm1
442 bookmark: bm2
447 bookmark: bm2
443 bookmark: bm3
448 bookmark: bm3
444 user: test
449 user: test
445 date: Thu Jan 01 00:00:00 1970 +0000
450 date: Thu Jan 01 00:00:00 1970 +0000
446 summary: 1
451 summary: 1
447
452
448 *** runcommand log
453 *** runcommand log
449 changeset: 0:eff892de26ec
454 changeset: 0:eff892de26ec
450 bookmark: bm1
455 bookmark: bm1
451 bookmark: bm2
456 bookmark: bm2
452 bookmark: bm3
457 bookmark: bm3
453 tag: tip
458 tag: tip
454 user: test
459 user: test
455 date: Thu Jan 01 00:00:00 1970 +0000
460 date: Thu Jan 01 00:00:00 1970 +0000
456 summary: 1
461 summary: 1
457
462
458
463
459 $ cat <<EOF >> .hg/hgrc
464 $ cat <<EOF >> .hg/hgrc
460 > [extensions]
465 > [extensions]
461 > mq =
466 > mq =
462 > EOF
467 > EOF
463
468
464 >>> import os
469 >>> import os
465 >>> from hgclient import readchannel, runcommand, check
470 >>> from hgclient import readchannel, runcommand, check
466 >>> @check
471 >>> @check
467 ... def mqoutsidechanges(server):
472 ... def mqoutsidechanges(server):
468 ... readchannel(server)
473 ... readchannel(server)
469 ...
474 ...
470 ... # load repo.mq
475 ... # load repo.mq
471 ... runcommand(server, ['qapplied'])
476 ... runcommand(server, ['qapplied'])
472 ... os.system('hg qnew 0.diff')
477 ... os.system('hg qnew 0.diff')
473 ... # repo.mq should be invalidated
478 ... # repo.mq should be invalidated
474 ... runcommand(server, ['qapplied'])
479 ... runcommand(server, ['qapplied'])
475 ...
480 ...
476 ... runcommand(server, ['qpop', '--all'])
481 ... runcommand(server, ['qpop', '--all'])
477 ... os.system('hg qqueue --create foo')
482 ... os.system('hg qqueue --create foo')
478 ... # repo.mq should be recreated to point to new queue
483 ... # repo.mq should be recreated to point to new queue
479 ... runcommand(server, ['qqueue', '--active'])
484 ... runcommand(server, ['qqueue', '--active'])
480 *** runcommand qapplied
485 *** runcommand qapplied
481 *** runcommand qapplied
486 *** runcommand qapplied
482 0.diff
487 0.diff
483 *** runcommand qpop --all
488 *** runcommand qpop --all
484 popping 0.diff
489 popping 0.diff
485 patch queue now empty
490 patch queue now empty
486 *** runcommand qqueue --active
491 *** runcommand qqueue --active
487 foo
492 foo
488
493
489 $ cat <<EOF > dbgui.py
494 $ cat <<EOF > dbgui.py
490 > from mercurial import cmdutil, commands
495 > from mercurial import cmdutil, commands
491 > cmdtable = {}
496 > cmdtable = {}
492 > command = cmdutil.command(cmdtable)
497 > command = cmdutil.command(cmdtable)
493 > @command("debuggetpass", norepo=True)
498 > @command("debuggetpass", norepo=True)
494 > def debuggetpass(ui):
499 > def debuggetpass(ui):
495 > ui.write("%s\\n" % ui.getpass())
500 > ui.write("%s\\n" % ui.getpass())
496 > @command("debugprompt", norepo=True)
501 > @command("debugprompt", norepo=True)
497 > def debugprompt(ui):
502 > def debugprompt(ui):
498 > ui.write("%s\\n" % ui.prompt("prompt:"))
503 > ui.write("%s\\n" % ui.prompt("prompt:"))
499 > EOF
504 > EOF
500 $ cat <<EOF >> .hg/hgrc
505 $ cat <<EOF >> .hg/hgrc
501 > [extensions]
506 > [extensions]
502 > dbgui = dbgui.py
507 > dbgui = dbgui.py
503 > EOF
508 > EOF
504
509
505 >>> import cStringIO
510 >>> import cStringIO
506 >>> from hgclient import readchannel, runcommand, check
511 >>> from hgclient import readchannel, runcommand, check
507 >>> @check
512 >>> @check
508 ... def getpass(server):
513 ... def getpass(server):
509 ... readchannel(server)
514 ... readchannel(server)
510 ... runcommand(server, ['debuggetpass', '--config',
515 ... runcommand(server, ['debuggetpass', '--config',
511 ... 'ui.interactive=True'],
516 ... 'ui.interactive=True'],
512 ... input=cStringIO.StringIO('1234\n'))
517 ... input=cStringIO.StringIO('1234\n'))
513 ... runcommand(server, ['debugprompt', '--config',
518 ... runcommand(server, ['debugprompt', '--config',
514 ... 'ui.interactive=True'],
519 ... 'ui.interactive=True'],
515 ... input=cStringIO.StringIO('5678\n'))
520 ... input=cStringIO.StringIO('5678\n'))
516 *** runcommand debuggetpass --config ui.interactive=True
521 *** runcommand debuggetpass --config ui.interactive=True
517 password: 1234
522 password: 1234
518 *** runcommand debugprompt --config ui.interactive=True
523 *** runcommand debugprompt --config ui.interactive=True
519 prompt: 5678
524 prompt: 5678
520
525
521
526
522 start without repository:
527 start without repository:
523
528
524 $ cd ..
529 $ cd ..
525
530
526 >>> from hgclient import readchannel, runcommand, check
531 >>> from hgclient import readchannel, runcommand, check
527 >>> @check
532 >>> @check
528 ... def hellomessage(server):
533 ... def hellomessage(server):
529 ... ch, data = readchannel(server)
534 ... ch, data = readchannel(server)
530 ... print '%c, %r' % (ch, data)
535 ... print '%c, %r' % (ch, data)
531 ... # run an arbitrary command to make sure the next thing the server
536 ... # run an arbitrary command to make sure the next thing the server
532 ... # sends isn't part of the hello message
537 ... # sends isn't part of the hello message
533 ... runcommand(server, ['id'])
538 ... runcommand(server, ['id'])
534 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
539 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
535 *** runcommand id
540 *** runcommand id
536 abort: there is no Mercurial repository here (.hg not found)
541 abort: there is no Mercurial repository here (.hg not found)
537 [255]
542 [255]
538
543
539 >>> from hgclient import readchannel, runcommand, check
544 >>> from hgclient import readchannel, runcommand, check
540 >>> @check
545 >>> @check
541 ... def startwithoutrepo(server):
546 ... def startwithoutrepo(server):
542 ... readchannel(server)
547 ... readchannel(server)
543 ... runcommand(server, ['init', 'repo2'])
548 ... runcommand(server, ['init', 'repo2'])
544 ... runcommand(server, ['id', '-R', 'repo2'])
549 ... runcommand(server, ['id', '-R', 'repo2'])
545 *** runcommand init repo2
550 *** runcommand init repo2
546 *** runcommand id -R repo2
551 *** runcommand id -R repo2
547 000000000000 tip
552 000000000000 tip
548
553
549
554
550 unix domain socket:
555 unix domain socket:
551
556
552 $ cd repo
557 $ cd repo
553 $ hg update -q
558 $ hg update -q
554
559
555 #if unix-socket
560 #if unix-socket
556
561
557 >>> import cStringIO
562 >>> import cStringIO
558 >>> from hgclient import unixserver, readchannel, runcommand, check
563 >>> from hgclient import unixserver, readchannel, runcommand, check
559 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
564 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
560 >>> def hellomessage(conn):
565 >>> def hellomessage(conn):
561 ... ch, data = readchannel(conn)
566 ... ch, data = readchannel(conn)
562 ... print '%c, %r' % (ch, data)
567 ... print '%c, %r' % (ch, data)
563 ... runcommand(conn, ['id'])
568 ... runcommand(conn, ['id'])
564 >>> check(hellomessage, server.connect)
569 >>> check(hellomessage, server.connect)
565 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
570 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
566 *** runcommand id
571 *** runcommand id
567 eff892de26ec tip bm1/bm2/bm3
572 eff892de26ec tip bm1/bm2/bm3
568 >>> def unknowncommand(conn):
573 >>> def unknowncommand(conn):
569 ... readchannel(conn)
574 ... readchannel(conn)
570 ... conn.stdin.write('unknowncommand\n')
575 ... conn.stdin.write('unknowncommand\n')
571 >>> check(unknowncommand, server.connect) # error sent to server.log
576 >>> check(unknowncommand, server.connect) # error sent to server.log
572 >>> def serverinput(conn):
577 >>> def serverinput(conn):
573 ... readchannel(conn)
578 ... readchannel(conn)
574 ... patch = """
579 ... patch = """
575 ... # HG changeset patch
580 ... # HG changeset patch
576 ... # User test
581 ... # User test
577 ... # Date 0 0
582 ... # Date 0 0
578 ... 2
583 ... 2
579 ...
584 ...
580 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
585 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
581 ... --- a/a
586 ... --- a/a
582 ... +++ b/a
587 ... +++ b/a
583 ... @@ -1,1 +1,2 @@
588 ... @@ -1,1 +1,2 @@
584 ... 1
589 ... 1
585 ... +2
590 ... +2
586 ... """
591 ... """
587 ... runcommand(conn, ['import', '-'], input=cStringIO.StringIO(patch))
592 ... runcommand(conn, ['import', '-'], input=cStringIO.StringIO(patch))
588 ... runcommand(conn, ['log', '-rtip', '-q'])
593 ... runcommand(conn, ['log', '-rtip', '-q'])
589 >>> check(serverinput, server.connect)
594 >>> check(serverinput, server.connect)
590 *** runcommand import -
595 *** runcommand import -
591 applying patch from stdin
596 applying patch from stdin
592 *** runcommand log -rtip -q
597 *** runcommand log -rtip -q
593 2:1ed24be7e7a0
598 2:1ed24be7e7a0
594 >>> server.shutdown()
599 >>> server.shutdown()
595
600
596 $ cat .hg/server.log
601 $ cat .hg/server.log
597 listening at .hg/server.sock
602 listening at .hg/server.sock
598 abort: unknown command unknowncommand
603 abort: unknown command unknowncommand
599 killed!
604 killed!
600
605
601 #else
606 #else
602
607
603 $ hg serve --cmdserver unix -a .hg/server.sock
608 $ hg serve --cmdserver unix -a .hg/server.sock
604 abort: unsupported platform
609 abort: unsupported platform
605 [255]
610 [255]
606
611
607 #endif
612 #endif
General Comments 0
You need to be logged in to leave comments. Login now