##// END OF EJS Templates
py3: use py3 as the test tag, dropping the k...
Martijn Pieters -
r40299:8cf459d8 default
parent child Browse files
Show More
@@ -1,694 +1,694 b''
1 # help.py - help data for mercurial
1 # help.py - help data for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import itertools
10 import itertools
11 import os
11 import os
12 import textwrap
12 import textwrap
13
13
14 from .i18n import (
14 from .i18n import (
15 _,
15 _,
16 gettext,
16 gettext,
17 )
17 )
18 from . import (
18 from . import (
19 cmdutil,
19 cmdutil,
20 encoding,
20 encoding,
21 error,
21 error,
22 extensions,
22 extensions,
23 fancyopts,
23 fancyopts,
24 filemerge,
24 filemerge,
25 fileset,
25 fileset,
26 minirst,
26 minirst,
27 pycompat,
27 pycompat,
28 revset,
28 revset,
29 templatefilters,
29 templatefilters,
30 templatefuncs,
30 templatefuncs,
31 templatekw,
31 templatekw,
32 util,
32 util,
33 )
33 )
34 from .hgweb import (
34 from .hgweb import (
35 webcommands,
35 webcommands,
36 )
36 )
37
37
38 _exclkeywords = {
38 _exclkeywords = {
39 "(ADVANCED)",
39 "(ADVANCED)",
40 "(DEPRECATED)",
40 "(DEPRECATED)",
41 "(EXPERIMENTAL)",
41 "(EXPERIMENTAL)",
42 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
42 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
43 _("(ADVANCED)"),
43 _("(ADVANCED)"),
44 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
44 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
45 _("(DEPRECATED)"),
45 _("(DEPRECATED)"),
46 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
46 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
47 _("(EXPERIMENTAL)"),
47 _("(EXPERIMENTAL)"),
48 }
48 }
49
49
50 def listexts(header, exts, indent=1, showdeprecated=False):
50 def listexts(header, exts, indent=1, showdeprecated=False):
51 '''return a text listing of the given extensions'''
51 '''return a text listing of the given extensions'''
52 rst = []
52 rst = []
53 if exts:
53 if exts:
54 for name, desc in sorted(exts.iteritems()):
54 for name, desc in sorted(exts.iteritems()):
55 if not showdeprecated and any(w in desc for w in _exclkeywords):
55 if not showdeprecated and any(w in desc for w in _exclkeywords):
56 continue
56 continue
57 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
57 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
58 if rst:
58 if rst:
59 rst.insert(0, '\n%s\n\n' % header)
59 rst.insert(0, '\n%s\n\n' % header)
60 return rst
60 return rst
61
61
62 def extshelp(ui):
62 def extshelp(ui):
63 rst = loaddoc('extensions')(ui).splitlines(True)
63 rst = loaddoc('extensions')(ui).splitlines(True)
64 rst.extend(listexts(
64 rst.extend(listexts(
65 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
65 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
66 rst.extend(listexts(_('disabled extensions:'), extensions.disabled(),
66 rst.extend(listexts(_('disabled extensions:'), extensions.disabled(),
67 showdeprecated=ui.verbose))
67 showdeprecated=ui.verbose))
68 doc = ''.join(rst)
68 doc = ''.join(rst)
69 return doc
69 return doc
70
70
71 def optrst(header, options, verbose):
71 def optrst(header, options, verbose):
72 data = []
72 data = []
73 multioccur = False
73 multioccur = False
74 for option in options:
74 for option in options:
75 if len(option) == 5:
75 if len(option) == 5:
76 shortopt, longopt, default, desc, optlabel = option
76 shortopt, longopt, default, desc, optlabel = option
77 else:
77 else:
78 shortopt, longopt, default, desc = option
78 shortopt, longopt, default, desc = option
79 optlabel = _("VALUE") # default label
79 optlabel = _("VALUE") # default label
80
80
81 if not verbose and any(w in desc for w in _exclkeywords):
81 if not verbose and any(w in desc for w in _exclkeywords):
82 continue
82 continue
83
83
84 so = ''
84 so = ''
85 if shortopt:
85 if shortopt:
86 so = '-' + shortopt
86 so = '-' + shortopt
87 lo = '--' + longopt
87 lo = '--' + longopt
88
88
89 if isinstance(default, fancyopts.customopt):
89 if isinstance(default, fancyopts.customopt):
90 default = default.getdefaultvalue()
90 default = default.getdefaultvalue()
91 if default and not callable(default):
91 if default and not callable(default):
92 # default is of unknown type, and in Python 2 we abused
92 # default is of unknown type, and in Python 2 we abused
93 # the %s-shows-repr property to handle integers etc. To
93 # the %s-shows-repr property to handle integers etc. To
94 # match that behavior on Python 3, we do str(default) and
94 # match that behavior on Python 3, we do str(default) and
95 # then convert it to bytes.
95 # then convert it to bytes.
96 desc += _(" (default: %s)") % pycompat.bytestr(default)
96 desc += _(" (default: %s)") % pycompat.bytestr(default)
97
97
98 if isinstance(default, list):
98 if isinstance(default, list):
99 lo += " %s [+]" % optlabel
99 lo += " %s [+]" % optlabel
100 multioccur = True
100 multioccur = True
101 elif (default is not None) and not isinstance(default, bool):
101 elif (default is not None) and not isinstance(default, bool):
102 lo += " %s" % optlabel
102 lo += " %s" % optlabel
103
103
104 data.append((so, lo, desc))
104 data.append((so, lo, desc))
105
105
106 if multioccur:
106 if multioccur:
107 header += (_(" ([+] can be repeated)"))
107 header += (_(" ([+] can be repeated)"))
108
108
109 rst = ['\n%s:\n\n' % header]
109 rst = ['\n%s:\n\n' % header]
110 rst.extend(minirst.maketable(data, 1))
110 rst.extend(minirst.maketable(data, 1))
111
111
112 return ''.join(rst)
112 return ''.join(rst)
113
113
114 def indicateomitted(rst, omitted, notomitted=None):
114 def indicateomitted(rst, omitted, notomitted=None):
115 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
115 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
116 if notomitted:
116 if notomitted:
117 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
117 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
118
118
119 def filtercmd(ui, cmd, kw, doc):
119 def filtercmd(ui, cmd, kw, doc):
120 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
120 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
121 return True
121 return True
122 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
122 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
123 return True
123 return True
124 return False
124 return False
125
125
126 def topicmatch(ui, commands, kw):
126 def topicmatch(ui, commands, kw):
127 """Return help topics matching kw.
127 """Return help topics matching kw.
128
128
129 Returns {'section': [(name, summary), ...], ...} where section is
129 Returns {'section': [(name, summary), ...], ...} where section is
130 one of topics, commands, extensions, or extensioncommands.
130 one of topics, commands, extensions, or extensioncommands.
131 """
131 """
132 kw = encoding.lower(kw)
132 kw = encoding.lower(kw)
133 def lowercontains(container):
133 def lowercontains(container):
134 return kw in encoding.lower(container) # translated in helptable
134 return kw in encoding.lower(container) # translated in helptable
135 results = {'topics': [],
135 results = {'topics': [],
136 'commands': [],
136 'commands': [],
137 'extensions': [],
137 'extensions': [],
138 'extensioncommands': [],
138 'extensioncommands': [],
139 }
139 }
140 for names, header, doc in helptable:
140 for names, header, doc in helptable:
141 # Old extensions may use a str as doc.
141 # Old extensions may use a str as doc.
142 if (sum(map(lowercontains, names))
142 if (sum(map(lowercontains, names))
143 or lowercontains(header)
143 or lowercontains(header)
144 or (callable(doc) and lowercontains(doc(ui)))):
144 or (callable(doc) and lowercontains(doc(ui)))):
145 results['topics'].append((names[0], header))
145 results['topics'].append((names[0], header))
146 for cmd, entry in commands.table.iteritems():
146 for cmd, entry in commands.table.iteritems():
147 if len(entry) == 3:
147 if len(entry) == 3:
148 summary = entry[2]
148 summary = entry[2]
149 else:
149 else:
150 summary = ''
150 summary = ''
151 # translate docs *before* searching there
151 # translate docs *before* searching there
152 docs = _(pycompat.getdoc(entry[0])) or ''
152 docs = _(pycompat.getdoc(entry[0])) or ''
153 if kw in cmd or lowercontains(summary) or lowercontains(docs):
153 if kw in cmd or lowercontains(summary) or lowercontains(docs):
154 doclines = docs.splitlines()
154 doclines = docs.splitlines()
155 if doclines:
155 if doclines:
156 summary = doclines[0]
156 summary = doclines[0]
157 cmdname = cmdutil.parsealiases(cmd)[0]
157 cmdname = cmdutil.parsealiases(cmd)[0]
158 if filtercmd(ui, cmdname, kw, docs):
158 if filtercmd(ui, cmdname, kw, docs):
159 continue
159 continue
160 results['commands'].append((cmdname, summary))
160 results['commands'].append((cmdname, summary))
161 for name, docs in itertools.chain(
161 for name, docs in itertools.chain(
162 extensions.enabled(False).iteritems(),
162 extensions.enabled(False).iteritems(),
163 extensions.disabled().iteritems()):
163 extensions.disabled().iteritems()):
164 if not docs:
164 if not docs:
165 continue
165 continue
166 name = name.rpartition('.')[-1]
166 name = name.rpartition('.')[-1]
167 if lowercontains(name) or lowercontains(docs):
167 if lowercontains(name) or lowercontains(docs):
168 # extension docs are already translated
168 # extension docs are already translated
169 results['extensions'].append((name, docs.splitlines()[0]))
169 results['extensions'].append((name, docs.splitlines()[0]))
170 try:
170 try:
171 mod = extensions.load(ui, name, '')
171 mod = extensions.load(ui, name, '')
172 except ImportError:
172 except ImportError:
173 # debug message would be printed in extensions.load()
173 # debug message would be printed in extensions.load()
174 continue
174 continue
175 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
175 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
176 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
176 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
177 cmdname = cmdutil.parsealiases(cmd)[0]
177 cmdname = cmdutil.parsealiases(cmd)[0]
178 cmddoc = pycompat.getdoc(entry[0])
178 cmddoc = pycompat.getdoc(entry[0])
179 if cmddoc:
179 if cmddoc:
180 cmddoc = gettext(cmddoc).splitlines()[0]
180 cmddoc = gettext(cmddoc).splitlines()[0]
181 else:
181 else:
182 cmddoc = _('(no help text available)')
182 cmddoc = _('(no help text available)')
183 if filtercmd(ui, cmdname, kw, cmddoc):
183 if filtercmd(ui, cmdname, kw, cmddoc):
184 continue
184 continue
185 results['extensioncommands'].append((cmdname, cmddoc))
185 results['extensioncommands'].append((cmdname, cmddoc))
186 return results
186 return results
187
187
188 def loaddoc(topic, subdir=None):
188 def loaddoc(topic, subdir=None):
189 """Return a delayed loader for help/topic.txt."""
189 """Return a delayed loader for help/topic.txt."""
190
190
191 def loader(ui):
191 def loader(ui):
192 docdir = os.path.join(util.datapath, 'help')
192 docdir = os.path.join(util.datapath, 'help')
193 if subdir:
193 if subdir:
194 docdir = os.path.join(docdir, subdir)
194 docdir = os.path.join(docdir, subdir)
195 path = os.path.join(docdir, topic + ".txt")
195 path = os.path.join(docdir, topic + ".txt")
196 doc = gettext(util.readfile(path))
196 doc = gettext(util.readfile(path))
197 for rewriter in helphooks.get(topic, []):
197 for rewriter in helphooks.get(topic, []):
198 doc = rewriter(ui, topic, doc)
198 doc = rewriter(ui, topic, doc)
199 return doc
199 return doc
200
200
201 return loader
201 return loader
202
202
203 internalstable = sorted([
203 internalstable = sorted([
204 (['bundle2'], _('Bundle2'),
204 (['bundle2'], _('Bundle2'),
205 loaddoc('bundle2', subdir='internals')),
205 loaddoc('bundle2', subdir='internals')),
206 (['bundles'], _('Bundles'),
206 (['bundles'], _('Bundles'),
207 loaddoc('bundles', subdir='internals')),
207 loaddoc('bundles', subdir='internals')),
208 (['cbor'], _('CBOR'),
208 (['cbor'], _('CBOR'),
209 loaddoc('cbor', subdir='internals')),
209 loaddoc('cbor', subdir='internals')),
210 (['censor'], _('Censor'),
210 (['censor'], _('Censor'),
211 loaddoc('censor', subdir='internals')),
211 loaddoc('censor', subdir='internals')),
212 (['changegroups'], _('Changegroups'),
212 (['changegroups'], _('Changegroups'),
213 loaddoc('changegroups', subdir='internals')),
213 loaddoc('changegroups', subdir='internals')),
214 (['config'], _('Config Registrar'),
214 (['config'], _('Config Registrar'),
215 loaddoc('config', subdir='internals')),
215 loaddoc('config', subdir='internals')),
216 (['requirements'], _('Repository Requirements'),
216 (['requirements'], _('Repository Requirements'),
217 loaddoc('requirements', subdir='internals')),
217 loaddoc('requirements', subdir='internals')),
218 (['revlogs'], _('Revision Logs'),
218 (['revlogs'], _('Revision Logs'),
219 loaddoc('revlogs', subdir='internals')),
219 loaddoc('revlogs', subdir='internals')),
220 (['wireprotocol'], _('Wire Protocol'),
220 (['wireprotocol'], _('Wire Protocol'),
221 loaddoc('wireprotocol', subdir='internals')),
221 loaddoc('wireprotocol', subdir='internals')),
222 (['wireprotocolrpc'], _('Wire Protocol RPC'),
222 (['wireprotocolrpc'], _('Wire Protocol RPC'),
223 loaddoc('wireprotocolrpc', subdir='internals')),
223 loaddoc('wireprotocolrpc', subdir='internals')),
224 (['wireprotocolv2'], _('Wire Protocol Version 2'),
224 (['wireprotocolv2'], _('Wire Protocol Version 2'),
225 loaddoc('wireprotocolv2', subdir='internals')),
225 loaddoc('wireprotocolv2', subdir='internals')),
226 ])
226 ])
227
227
228 def internalshelp(ui):
228 def internalshelp(ui):
229 """Generate the index for the "internals" topic."""
229 """Generate the index for the "internals" topic."""
230 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n',
230 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n',
231 '\n']
231 '\n']
232 for names, header, doc in internalstable:
232 for names, header, doc in internalstable:
233 lines.append(' :%s: %s\n' % (names[0], header))
233 lines.append(' :%s: %s\n' % (names[0], header))
234
234
235 return ''.join(lines)
235 return ''.join(lines)
236
236
237 helptable = sorted([
237 helptable = sorted([
238 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec')),
238 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec')),
239 (['color'], _("Colorizing Outputs"), loaddoc('color')),
239 (['color'], _("Colorizing Outputs"), loaddoc('color')),
240 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
240 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
241 (['deprecated'], _("Deprecated Features"), loaddoc('deprecated')),
241 (['deprecated'], _("Deprecated Features"), loaddoc('deprecated')),
242 (["dates"], _("Date Formats"), loaddoc('dates')),
242 (["dates"], _("Date Formats"), loaddoc('dates')),
243 (["flags"], _("Command-line flags"), loaddoc('flags')),
243 (["flags"], _("Command-line flags"), loaddoc('flags')),
244 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
244 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
245 (['environment', 'env'], _('Environment Variables'),
245 (['environment', 'env'], _('Environment Variables'),
246 loaddoc('environment')),
246 loaddoc('environment')),
247 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
247 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
248 _('Specifying Revisions'), loaddoc('revisions')),
248 _('Specifying Revisions'), loaddoc('revisions')),
249 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
249 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
250 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
250 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
251 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
251 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
252 loaddoc('merge-tools')),
252 loaddoc('merge-tools')),
253 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
253 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
254 loaddoc('templates')),
254 loaddoc('templates')),
255 (['urls'], _('URL Paths'), loaddoc('urls')),
255 (['urls'], _('URL Paths'), loaddoc('urls')),
256 (["extensions"], _("Using Additional Features"), extshelp),
256 (["extensions"], _("Using Additional Features"), extshelp),
257 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
257 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
258 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
258 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
259 (["glossary"], _("Glossary"), loaddoc('glossary')),
259 (["glossary"], _("Glossary"), loaddoc('glossary')),
260 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
260 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
261 loaddoc('hgignore')),
261 loaddoc('hgignore')),
262 (["phases"], _("Working with Phases"), loaddoc('phases')),
262 (["phases"], _("Working with Phases"), loaddoc('phases')),
263 (['scripting'], _('Using Mercurial from scripts and automation'),
263 (['scripting'], _('Using Mercurial from scripts and automation'),
264 loaddoc('scripting')),
264 loaddoc('scripting')),
265 (['internals'], _("Technical implementation topics"),
265 (['internals'], _("Technical implementation topics"),
266 internalshelp),
266 internalshelp),
267 (['pager'], _("Pager Support"), loaddoc('pager')),
267 (['pager'], _("Pager Support"), loaddoc('pager')),
268 ])
268 ])
269
269
270 # Maps topics with sub-topics to a list of their sub-topics.
270 # Maps topics with sub-topics to a list of their sub-topics.
271 subtopics = {
271 subtopics = {
272 'internals': internalstable,
272 'internals': internalstable,
273 }
273 }
274
274
275 # Map topics to lists of callable taking the current topic help and
275 # Map topics to lists of callable taking the current topic help and
276 # returning the updated version
276 # returning the updated version
277 helphooks = {}
277 helphooks = {}
278
278
279 def addtopichook(topic, rewriter):
279 def addtopichook(topic, rewriter):
280 helphooks.setdefault(topic, []).append(rewriter)
280 helphooks.setdefault(topic, []).append(rewriter)
281
281
282 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
282 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
283 """Extract docstring from the items key to function mapping, build a
283 """Extract docstring from the items key to function mapping, build a
284 single documentation block and use it to overwrite the marker in doc.
284 single documentation block and use it to overwrite the marker in doc.
285 """
285 """
286 entries = []
286 entries = []
287 for name in sorted(items):
287 for name in sorted(items):
288 text = (pycompat.getdoc(items[name]) or '').rstrip()
288 text = (pycompat.getdoc(items[name]) or '').rstrip()
289 if (not text
289 if (not text
290 or not ui.verbose and any(w in text for w in _exclkeywords)):
290 or not ui.verbose and any(w in text for w in _exclkeywords)):
291 continue
291 continue
292 text = gettext(text)
292 text = gettext(text)
293 if dedent:
293 if dedent:
294 # Abuse latin1 to use textwrap.dedent() on bytes.
294 # Abuse latin1 to use textwrap.dedent() on bytes.
295 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
295 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
296 lines = text.splitlines()
296 lines = text.splitlines()
297 doclines = [(lines[0])]
297 doclines = [(lines[0])]
298 for l in lines[1:]:
298 for l in lines[1:]:
299 # Stop once we find some Python doctest
299 # Stop once we find some Python doctest
300 if l.strip().startswith('>>>'):
300 if l.strip().startswith('>>>'):
301 break
301 break
302 if dedent:
302 if dedent:
303 doclines.append(l.rstrip())
303 doclines.append(l.rstrip())
304 else:
304 else:
305 doclines.append(' ' + l.strip())
305 doclines.append(' ' + l.strip())
306 entries.append('\n'.join(doclines))
306 entries.append('\n'.join(doclines))
307 entries = '\n\n'.join(entries)
307 entries = '\n\n'.join(entries)
308 return doc.replace(marker, entries)
308 return doc.replace(marker, entries)
309
309
310 def addtopicsymbols(topic, marker, symbols, dedent=False):
310 def addtopicsymbols(topic, marker, symbols, dedent=False):
311 def add(ui, topic, doc):
311 def add(ui, topic, doc):
312 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
312 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
313 addtopichook(topic, add)
313 addtopichook(topic, add)
314
314
315 addtopicsymbols('bundlespec', '.. bundlecompressionmarker',
315 addtopicsymbols('bundlespec', '.. bundlecompressionmarker',
316 util.bundlecompressiontopics())
316 util.bundlecompressiontopics())
317 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
317 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
318 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
318 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
319 filemerge.internalsdoc)
319 filemerge.internalsdoc)
320 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
320 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
321 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
321 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
322 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
322 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
323 addtopicsymbols('templates', '.. functionsmarker', templatefuncs.funcs)
323 addtopicsymbols('templates', '.. functionsmarker', templatefuncs.funcs)
324 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
324 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
325 dedent=True)
325 dedent=True)
326
326
327 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None,
327 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None,
328 **opts):
328 **opts):
329 '''
329 '''
330 Generate the help for 'name' as unformatted restructured text. If
330 Generate the help for 'name' as unformatted restructured text. If
331 'name' is None, describe the commands available.
331 'name' is None, describe the commands available.
332 '''
332 '''
333
333
334 opts = pycompat.byteskwargs(opts)
334 opts = pycompat.byteskwargs(opts)
335
335
336 def helpcmd(name, subtopic=None):
336 def helpcmd(name, subtopic=None):
337 try:
337 try:
338 aliases, entry = cmdutil.findcmd(name, commands.table,
338 aliases, entry = cmdutil.findcmd(name, commands.table,
339 strict=unknowncmd)
339 strict=unknowncmd)
340 except error.AmbiguousCommand as inst:
340 except error.AmbiguousCommand as inst:
341 # py3k fix: except vars can't be used outside the scope of the
341 # py3 fix: except vars can't be used outside the scope of the
342 # except block, nor can be used inside a lambda. python issue4617
342 # except block, nor can be used inside a lambda. python issue4617
343 prefix = inst.args[0]
343 prefix = inst.args[0]
344 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix)
344 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix)
345 rst = helplist(select)
345 rst = helplist(select)
346 return rst
346 return rst
347
347
348 rst = []
348 rst = []
349
349
350 # check if it's an invalid alias and display its error if it is
350 # check if it's an invalid alias and display its error if it is
351 if getattr(entry[0], 'badalias', None):
351 if getattr(entry[0], 'badalias', None):
352 rst.append(entry[0].badalias + '\n')
352 rst.append(entry[0].badalias + '\n')
353 if entry[0].unknowncmd:
353 if entry[0].unknowncmd:
354 try:
354 try:
355 rst.extend(helpextcmd(entry[0].cmdname))
355 rst.extend(helpextcmd(entry[0].cmdname))
356 except error.UnknownCommand:
356 except error.UnknownCommand:
357 pass
357 pass
358 return rst
358 return rst
359
359
360 # synopsis
360 # synopsis
361 if len(entry) > 2:
361 if len(entry) > 2:
362 if entry[2].startswith('hg'):
362 if entry[2].startswith('hg'):
363 rst.append("%s\n" % entry[2])
363 rst.append("%s\n" % entry[2])
364 else:
364 else:
365 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
365 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
366 else:
366 else:
367 rst.append('hg %s\n' % aliases[0])
367 rst.append('hg %s\n' % aliases[0])
368 # aliases
368 # aliases
369 if full and not ui.quiet and len(aliases) > 1:
369 if full and not ui.quiet and len(aliases) > 1:
370 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
370 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
371 rst.append('\n')
371 rst.append('\n')
372
372
373 # description
373 # description
374 doc = gettext(pycompat.getdoc(entry[0]))
374 doc = gettext(pycompat.getdoc(entry[0]))
375 if not doc:
375 if not doc:
376 doc = _("(no help text available)")
376 doc = _("(no help text available)")
377 if util.safehasattr(entry[0], 'definition'): # aliased command
377 if util.safehasattr(entry[0], 'definition'): # aliased command
378 source = entry[0].source
378 source = entry[0].source
379 if entry[0].definition.startswith('!'): # shell alias
379 if entry[0].definition.startswith('!'): # shell alias
380 doc = (_('shell alias for: %s\n\n%s\n\ndefined by: %s\n') %
380 doc = (_('shell alias for: %s\n\n%s\n\ndefined by: %s\n') %
381 (entry[0].definition[1:], doc, source))
381 (entry[0].definition[1:], doc, source))
382 else:
382 else:
383 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
383 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
384 (entry[0].definition, doc, source))
384 (entry[0].definition, doc, source))
385 doc = doc.splitlines(True)
385 doc = doc.splitlines(True)
386 if ui.quiet or not full:
386 if ui.quiet or not full:
387 rst.append(doc[0])
387 rst.append(doc[0])
388 else:
388 else:
389 rst.extend(doc)
389 rst.extend(doc)
390 rst.append('\n')
390 rst.append('\n')
391
391
392 # check if this command shadows a non-trivial (multi-line)
392 # check if this command shadows a non-trivial (multi-line)
393 # extension help text
393 # extension help text
394 try:
394 try:
395 mod = extensions.find(name)
395 mod = extensions.find(name)
396 doc = gettext(pycompat.getdoc(mod)) or ''
396 doc = gettext(pycompat.getdoc(mod)) or ''
397 if '\n' in doc.strip():
397 if '\n' in doc.strip():
398 msg = _("(use 'hg help -e %s' to show help for "
398 msg = _("(use 'hg help -e %s' to show help for "
399 "the %s extension)") % (name, name)
399 "the %s extension)") % (name, name)
400 rst.append('\n%s\n' % msg)
400 rst.append('\n%s\n' % msg)
401 except KeyError:
401 except KeyError:
402 pass
402 pass
403
403
404 # options
404 # options
405 if not ui.quiet and entry[1]:
405 if not ui.quiet and entry[1]:
406 rst.append(optrst(_("options"), entry[1], ui.verbose))
406 rst.append(optrst(_("options"), entry[1], ui.verbose))
407
407
408 if ui.verbose:
408 if ui.verbose:
409 rst.append(optrst(_("global options"),
409 rst.append(optrst(_("global options"),
410 commands.globalopts, ui.verbose))
410 commands.globalopts, ui.verbose))
411
411
412 if not ui.verbose:
412 if not ui.verbose:
413 if not full:
413 if not full:
414 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
414 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
415 % name)
415 % name)
416 elif not ui.quiet:
416 elif not ui.quiet:
417 rst.append(_('\n(some details hidden, use --verbose '
417 rst.append(_('\n(some details hidden, use --verbose '
418 'to show complete help)'))
418 'to show complete help)'))
419
419
420 return rst
420 return rst
421
421
422
422
423 def helplist(select=None, **opts):
423 def helplist(select=None, **opts):
424 # list of commands
424 # list of commands
425 if name == "shortlist":
425 if name == "shortlist":
426 header = _('basic commands:\n\n')
426 header = _('basic commands:\n\n')
427 elif name == "debug":
427 elif name == "debug":
428 header = _('debug commands (internal and unsupported):\n\n')
428 header = _('debug commands (internal and unsupported):\n\n')
429 else:
429 else:
430 header = _('list of commands:\n\n')
430 header = _('list of commands:\n\n')
431
431
432 h = {}
432 h = {}
433 cmds = {}
433 cmds = {}
434 for c, e in commands.table.iteritems():
434 for c, e in commands.table.iteritems():
435 fs = cmdutil.parsealiases(c)
435 fs = cmdutil.parsealiases(c)
436 f = fs[0]
436 f = fs[0]
437 p = ''
437 p = ''
438 if c.startswith("^"):
438 if c.startswith("^"):
439 p = '^'
439 p = '^'
440 if select and not select(p + f):
440 if select and not select(p + f):
441 continue
441 continue
442 if (not select and name != 'shortlist' and
442 if (not select and name != 'shortlist' and
443 e[0].__module__ != commands.__name__):
443 e[0].__module__ != commands.__name__):
444 continue
444 continue
445 if name == "shortlist" and not p:
445 if name == "shortlist" and not p:
446 continue
446 continue
447 doc = pycompat.getdoc(e[0])
447 doc = pycompat.getdoc(e[0])
448 if filtercmd(ui, f, name, doc):
448 if filtercmd(ui, f, name, doc):
449 continue
449 continue
450 doc = gettext(doc)
450 doc = gettext(doc)
451 if not doc:
451 if not doc:
452 doc = _("(no help text available)")
452 doc = _("(no help text available)")
453 h[f] = doc.splitlines()[0].rstrip()
453 h[f] = doc.splitlines()[0].rstrip()
454 cmds[f] = '|'.join(fs)
454 cmds[f] = '|'.join(fs)
455
455
456 rst = []
456 rst = []
457 if not h:
457 if not h:
458 if not ui.quiet:
458 if not ui.quiet:
459 rst.append(_('no commands defined\n'))
459 rst.append(_('no commands defined\n'))
460 return rst
460 return rst
461
461
462 if not ui.quiet:
462 if not ui.quiet:
463 rst.append(header)
463 rst.append(header)
464 fns = sorted(h)
464 fns = sorted(h)
465 for f in fns:
465 for f in fns:
466 if ui.verbose:
466 if ui.verbose:
467 commacmds = cmds[f].replace("|",", ")
467 commacmds = cmds[f].replace("|",", ")
468 rst.append(" :%s: %s\n" % (commacmds, h[f]))
468 rst.append(" :%s: %s\n" % (commacmds, h[f]))
469 else:
469 else:
470 rst.append(' :%s: %s\n' % (f, h[f]))
470 rst.append(' :%s: %s\n' % (f, h[f]))
471
471
472 ex = opts.get
472 ex = opts.get
473 anyopts = (ex(r'keyword') or not (ex(r'command') or ex(r'extension')))
473 anyopts = (ex(r'keyword') or not (ex(r'command') or ex(r'extension')))
474 if not name and anyopts:
474 if not name and anyopts:
475 exts = listexts(_('enabled extensions:'), extensions.enabled())
475 exts = listexts(_('enabled extensions:'), extensions.enabled())
476 if exts:
476 if exts:
477 rst.append('\n')
477 rst.append('\n')
478 rst.extend(exts)
478 rst.extend(exts)
479
479
480 rst.append(_("\nadditional help topics:\n\n"))
480 rst.append(_("\nadditional help topics:\n\n"))
481 topics = []
481 topics = []
482 for names, header, doc in helptable:
482 for names, header, doc in helptable:
483 topics.append((names[0], header))
483 topics.append((names[0], header))
484 for t, desc in topics:
484 for t, desc in topics:
485 rst.append(" :%s: %s\n" % (t, desc))
485 rst.append(" :%s: %s\n" % (t, desc))
486
486
487 if ui.quiet:
487 if ui.quiet:
488 pass
488 pass
489 elif ui.verbose:
489 elif ui.verbose:
490 rst.append('\n%s\n' % optrst(_("global options"),
490 rst.append('\n%s\n' % optrst(_("global options"),
491 commands.globalopts, ui.verbose))
491 commands.globalopts, ui.verbose))
492 if name == 'shortlist':
492 if name == 'shortlist':
493 rst.append(_("\n(use 'hg help' for the full list "
493 rst.append(_("\n(use 'hg help' for the full list "
494 "of commands)\n"))
494 "of commands)\n"))
495 else:
495 else:
496 if name == 'shortlist':
496 if name == 'shortlist':
497 rst.append(_("\n(use 'hg help' for the full list of commands "
497 rst.append(_("\n(use 'hg help' for the full list of commands "
498 "or 'hg -v' for details)\n"))
498 "or 'hg -v' for details)\n"))
499 elif name and not full:
499 elif name and not full:
500 rst.append(_("\n(use 'hg help %s' to show the full help "
500 rst.append(_("\n(use 'hg help %s' to show the full help "
501 "text)\n") % name)
501 "text)\n") % name)
502 elif name and cmds and name in cmds.keys():
502 elif name and cmds and name in cmds.keys():
503 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
503 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
504 "aliases and global options)\n") % name)
504 "aliases and global options)\n") % name)
505 else:
505 else:
506 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
506 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
507 "and global options)\n")
507 "and global options)\n")
508 % (name and " " + name or ""))
508 % (name and " " + name or ""))
509 return rst
509 return rst
510
510
511 def helptopic(name, subtopic=None):
511 def helptopic(name, subtopic=None):
512 # Look for sub-topic entry first.
512 # Look for sub-topic entry first.
513 header, doc = None, None
513 header, doc = None, None
514 if subtopic and name in subtopics:
514 if subtopic and name in subtopics:
515 for names, header, doc in subtopics[name]:
515 for names, header, doc in subtopics[name]:
516 if subtopic in names:
516 if subtopic in names:
517 break
517 break
518
518
519 if not header:
519 if not header:
520 for names, header, doc in helptable:
520 for names, header, doc in helptable:
521 if name in names:
521 if name in names:
522 break
522 break
523 else:
523 else:
524 raise error.UnknownCommand(name)
524 raise error.UnknownCommand(name)
525
525
526 rst = [minirst.section(header)]
526 rst = [minirst.section(header)]
527
527
528 # description
528 # description
529 if not doc:
529 if not doc:
530 rst.append(" %s\n" % _("(no help text available)"))
530 rst.append(" %s\n" % _("(no help text available)"))
531 if callable(doc):
531 if callable(doc):
532 rst += [" %s\n" % l for l in doc(ui).splitlines()]
532 rst += [" %s\n" % l for l in doc(ui).splitlines()]
533
533
534 if not ui.verbose:
534 if not ui.verbose:
535 omitted = _('(some details hidden, use --verbose'
535 omitted = _('(some details hidden, use --verbose'
536 ' to show complete help)')
536 ' to show complete help)')
537 indicateomitted(rst, omitted)
537 indicateomitted(rst, omitted)
538
538
539 try:
539 try:
540 cmdutil.findcmd(name, commands.table)
540 cmdutil.findcmd(name, commands.table)
541 rst.append(_("\nuse 'hg help -c %s' to see help for "
541 rst.append(_("\nuse 'hg help -c %s' to see help for "
542 "the %s command\n") % (name, name))
542 "the %s command\n") % (name, name))
543 except error.UnknownCommand:
543 except error.UnknownCommand:
544 pass
544 pass
545 return rst
545 return rst
546
546
547 def helpext(name, subtopic=None):
547 def helpext(name, subtopic=None):
548 try:
548 try:
549 mod = extensions.find(name)
549 mod = extensions.find(name)
550 doc = gettext(pycompat.getdoc(mod)) or _('no help text available')
550 doc = gettext(pycompat.getdoc(mod)) or _('no help text available')
551 except KeyError:
551 except KeyError:
552 mod = None
552 mod = None
553 doc = extensions.disabledext(name)
553 doc = extensions.disabledext(name)
554 if not doc:
554 if not doc:
555 raise error.UnknownCommand(name)
555 raise error.UnknownCommand(name)
556
556
557 if '\n' not in doc:
557 if '\n' not in doc:
558 head, tail = doc, ""
558 head, tail = doc, ""
559 else:
559 else:
560 head, tail = doc.split('\n', 1)
560 head, tail = doc.split('\n', 1)
561 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
561 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
562 if tail:
562 if tail:
563 rst.extend(tail.splitlines(True))
563 rst.extend(tail.splitlines(True))
564 rst.append('\n')
564 rst.append('\n')
565
565
566 if not ui.verbose:
566 if not ui.verbose:
567 omitted = _('(some details hidden, use --verbose'
567 omitted = _('(some details hidden, use --verbose'
568 ' to show complete help)')
568 ' to show complete help)')
569 indicateomitted(rst, omitted)
569 indicateomitted(rst, omitted)
570
570
571 if mod:
571 if mod:
572 try:
572 try:
573 ct = mod.cmdtable
573 ct = mod.cmdtable
574 except AttributeError:
574 except AttributeError:
575 ct = {}
575 ct = {}
576 modcmds = set([c.partition('|')[0] for c in ct])
576 modcmds = set([c.partition('|')[0] for c in ct])
577 rst.extend(helplist(modcmds.__contains__))
577 rst.extend(helplist(modcmds.__contains__))
578 else:
578 else:
579 rst.append(_("(use 'hg help extensions' for information on enabling"
579 rst.append(_("(use 'hg help extensions' for information on enabling"
580 " extensions)\n"))
580 " extensions)\n"))
581 return rst
581 return rst
582
582
583 def helpextcmd(name, subtopic=None):
583 def helpextcmd(name, subtopic=None):
584 cmd, ext, doc = extensions.disabledcmd(ui, name,
584 cmd, ext, doc = extensions.disabledcmd(ui, name,
585 ui.configbool('ui', 'strict'))
585 ui.configbool('ui', 'strict'))
586 doc = doc.splitlines()[0]
586 doc = doc.splitlines()[0]
587
587
588 rst = listexts(_("'%s' is provided by the following "
588 rst = listexts(_("'%s' is provided by the following "
589 "extension:") % cmd, {ext: doc}, indent=4,
589 "extension:") % cmd, {ext: doc}, indent=4,
590 showdeprecated=True)
590 showdeprecated=True)
591 rst.append('\n')
591 rst.append('\n')
592 rst.append(_("(use 'hg help extensions' for information on enabling "
592 rst.append(_("(use 'hg help extensions' for information on enabling "
593 "extensions)\n"))
593 "extensions)\n"))
594 return rst
594 return rst
595
595
596
596
597 rst = []
597 rst = []
598 kw = opts.get('keyword')
598 kw = opts.get('keyword')
599 if kw or name is None and any(opts[o] for o in opts):
599 if kw or name is None and any(opts[o] for o in opts):
600 matches = topicmatch(ui, commands, name or '')
600 matches = topicmatch(ui, commands, name or '')
601 helpareas = []
601 helpareas = []
602 if opts.get('extension'):
602 if opts.get('extension'):
603 helpareas += [('extensions', _('Extensions'))]
603 helpareas += [('extensions', _('Extensions'))]
604 if opts.get('command'):
604 if opts.get('command'):
605 helpareas += [('commands', _('Commands'))]
605 helpareas += [('commands', _('Commands'))]
606 if not helpareas:
606 if not helpareas:
607 helpareas = [('topics', _('Topics')),
607 helpareas = [('topics', _('Topics')),
608 ('commands', _('Commands')),
608 ('commands', _('Commands')),
609 ('extensions', _('Extensions')),
609 ('extensions', _('Extensions')),
610 ('extensioncommands', _('Extension Commands'))]
610 ('extensioncommands', _('Extension Commands'))]
611 for t, title in helpareas:
611 for t, title in helpareas:
612 if matches[t]:
612 if matches[t]:
613 rst.append('%s:\n\n' % title)
613 rst.append('%s:\n\n' % title)
614 rst.extend(minirst.maketable(sorted(matches[t]), 1))
614 rst.extend(minirst.maketable(sorted(matches[t]), 1))
615 rst.append('\n')
615 rst.append('\n')
616 if not rst:
616 if not rst:
617 msg = _('no matches')
617 msg = _('no matches')
618 hint = _("try 'hg help' for a list of topics")
618 hint = _("try 'hg help' for a list of topics")
619 raise error.Abort(msg, hint=hint)
619 raise error.Abort(msg, hint=hint)
620 elif name and name != 'shortlist':
620 elif name and name != 'shortlist':
621 queries = []
621 queries = []
622 if unknowncmd:
622 if unknowncmd:
623 queries += [helpextcmd]
623 queries += [helpextcmd]
624 if opts.get('extension'):
624 if opts.get('extension'):
625 queries += [helpext]
625 queries += [helpext]
626 if opts.get('command'):
626 if opts.get('command'):
627 queries += [helpcmd]
627 queries += [helpcmd]
628 if not queries:
628 if not queries:
629 queries = (helptopic, helpcmd, helpext, helpextcmd)
629 queries = (helptopic, helpcmd, helpext, helpextcmd)
630 for f in queries:
630 for f in queries:
631 try:
631 try:
632 rst = f(name, subtopic)
632 rst = f(name, subtopic)
633 break
633 break
634 except error.UnknownCommand:
634 except error.UnknownCommand:
635 pass
635 pass
636 else:
636 else:
637 if unknowncmd:
637 if unknowncmd:
638 raise error.UnknownCommand(name)
638 raise error.UnknownCommand(name)
639 else:
639 else:
640 msg = _('no such help topic: %s') % name
640 msg = _('no such help topic: %s') % name
641 hint = _("try 'hg help --keyword %s'") % name
641 hint = _("try 'hg help --keyword %s'") % name
642 raise error.Abort(msg, hint=hint)
642 raise error.Abort(msg, hint=hint)
643 else:
643 else:
644 # program name
644 # program name
645 if not ui.quiet:
645 if not ui.quiet:
646 rst = [_("Mercurial Distributed SCM\n"), '\n']
646 rst = [_("Mercurial Distributed SCM\n"), '\n']
647 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
647 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
648
648
649 return ''.join(rst)
649 return ''.join(rst)
650
650
651 def formattedhelp(ui, commands, fullname, keep=None, unknowncmd=False,
651 def formattedhelp(ui, commands, fullname, keep=None, unknowncmd=False,
652 full=True, **opts):
652 full=True, **opts):
653 """get help for a given topic (as a dotted name) as rendered rst
653 """get help for a given topic (as a dotted name) as rendered rst
654
654
655 Either returns the rendered help text or raises an exception.
655 Either returns the rendered help text or raises an exception.
656 """
656 """
657 if keep is None:
657 if keep is None:
658 keep = []
658 keep = []
659 else:
659 else:
660 keep = list(keep) # make a copy so we can mutate this later
660 keep = list(keep) # make a copy so we can mutate this later
661
661
662 # <fullname> := <name>[.<subtopic][.<section>]
662 # <fullname> := <name>[.<subtopic][.<section>]
663 name = subtopic = section = None
663 name = subtopic = section = None
664 if fullname is not None:
664 if fullname is not None:
665 nameparts = fullname.split('.')
665 nameparts = fullname.split('.')
666 name = nameparts.pop(0)
666 name = nameparts.pop(0)
667 if nameparts and name in subtopics:
667 if nameparts and name in subtopics:
668 subtopic = nameparts.pop(0)
668 subtopic = nameparts.pop(0)
669 if nameparts:
669 if nameparts:
670 section = encoding.lower('.'.join(nameparts))
670 section = encoding.lower('.'.join(nameparts))
671
671
672 textwidth = ui.configint('ui', 'textwidth')
672 textwidth = ui.configint('ui', 'textwidth')
673 termwidth = ui.termwidth() - 2
673 termwidth = ui.termwidth() - 2
674 if textwidth <= 0 or termwidth < textwidth:
674 if textwidth <= 0 or termwidth < textwidth:
675 textwidth = termwidth
675 textwidth = termwidth
676 text = help_(ui, commands, name,
676 text = help_(ui, commands, name,
677 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
677 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
678
678
679 blocks, pruned = minirst.parse(text, keep=keep)
679 blocks, pruned = minirst.parse(text, keep=keep)
680 if 'verbose' in pruned:
680 if 'verbose' in pruned:
681 keep.append('omitted')
681 keep.append('omitted')
682 else:
682 else:
683 keep.append('notomitted')
683 keep.append('notomitted')
684 blocks, pruned = minirst.parse(text, keep=keep)
684 blocks, pruned = minirst.parse(text, keep=keep)
685 if section:
685 if section:
686 blocks = minirst.filtersections(blocks, section)
686 blocks = minirst.filtersections(blocks, section)
687
687
688 # We could have been given a weird ".foo" section without a name
688 # We could have been given a weird ".foo" section without a name
689 # to look for, or we could have simply failed to found "foo.bar"
689 # to look for, or we could have simply failed to found "foo.bar"
690 # because bar isn't a section of foo
690 # because bar isn't a section of foo
691 if section and not (blocks and name):
691 if section and not (blocks and name):
692 raise error.Abort(_("help section not found: %s") % fullname)
692 raise error.Abort(_("help section not found: %s") % fullname)
693
693
694 return minirst.formatplain(blocks, textwidth)
694 return minirst.formatplain(blocks, textwidth)
@@ -1,798 +1,798 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import errno
3 import errno
4 import os
4 import os
5 import re
5 import re
6 import socket
6 import socket
7 import stat
7 import stat
8 import subprocess
8 import subprocess
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11
11
12 tempprefix = 'hg-hghave-'
12 tempprefix = 'hg-hghave-'
13
13
14 checks = {
14 checks = {
15 "true": (lambda: True, "yak shaving"),
15 "true": (lambda: True, "yak shaving"),
16 "false": (lambda: False, "nail clipper"),
16 "false": (lambda: False, "nail clipper"),
17 }
17 }
18
18
19 if sys.version_info[0] >= 3:
19 if sys.version_info[0] >= 3:
20 def _bytespath(p):
20 def _bytespath(p):
21 if p is None:
21 if p is None:
22 return p
22 return p
23 return p.encode('utf-8')
23 return p.encode('utf-8')
24
24
25 def _strpath(p):
25 def _strpath(p):
26 if p is None:
26 if p is None:
27 return p
27 return p
28 return p.decode('utf-8')
28 return p.decode('utf-8')
29 else:
29 else:
30 def _bytespath(p):
30 def _bytespath(p):
31 return p
31 return p
32
32
33 _strpath = _bytespath
33 _strpath = _bytespath
34
34
35 def check(name, desc):
35 def check(name, desc):
36 """Registers a check function for a feature."""
36 """Registers a check function for a feature."""
37 def decorator(func):
37 def decorator(func):
38 checks[name] = (func, desc)
38 checks[name] = (func, desc)
39 return func
39 return func
40 return decorator
40 return decorator
41
41
42 def checkvers(name, desc, vers):
42 def checkvers(name, desc, vers):
43 """Registers a check function for each of a series of versions.
43 """Registers a check function for each of a series of versions.
44
44
45 vers can be a list or an iterator"""
45 vers can be a list or an iterator"""
46 def decorator(func):
46 def decorator(func):
47 def funcv(v):
47 def funcv(v):
48 def f():
48 def f():
49 return func(v)
49 return func(v)
50 return f
50 return f
51 for v in vers:
51 for v in vers:
52 v = str(v)
52 v = str(v)
53 f = funcv(v)
53 f = funcv(v)
54 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
54 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
55 return func
55 return func
56 return decorator
56 return decorator
57
57
58 def checkfeatures(features):
58 def checkfeatures(features):
59 result = {
59 result = {
60 'error': [],
60 'error': [],
61 'missing': [],
61 'missing': [],
62 'skipped': [],
62 'skipped': [],
63 }
63 }
64
64
65 for feature in features:
65 for feature in features:
66 negate = feature.startswith('no-')
66 negate = feature.startswith('no-')
67 if negate:
67 if negate:
68 feature = feature[3:]
68 feature = feature[3:]
69
69
70 if feature not in checks:
70 if feature not in checks:
71 result['missing'].append(feature)
71 result['missing'].append(feature)
72 continue
72 continue
73
73
74 check, desc = checks[feature]
74 check, desc = checks[feature]
75 try:
75 try:
76 available = check()
76 available = check()
77 except Exception:
77 except Exception:
78 result['error'].append('hghave check failed: %s' % feature)
78 result['error'].append('hghave check failed: %s' % feature)
79 continue
79 continue
80
80
81 if not negate and not available:
81 if not negate and not available:
82 result['skipped'].append('missing feature: %s' % desc)
82 result['skipped'].append('missing feature: %s' % desc)
83 elif negate and available:
83 elif negate and available:
84 result['skipped'].append('system supports %s' % desc)
84 result['skipped'].append('system supports %s' % desc)
85
85
86 return result
86 return result
87
87
88 def require(features):
88 def require(features):
89 """Require that features are available, exiting if not."""
89 """Require that features are available, exiting if not."""
90 result = checkfeatures(features)
90 result = checkfeatures(features)
91
91
92 for missing in result['missing']:
92 for missing in result['missing']:
93 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
93 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
94 for msg in result['skipped']:
94 for msg in result['skipped']:
95 sys.stderr.write('skipped: %s\n' % msg)
95 sys.stderr.write('skipped: %s\n' % msg)
96 for msg in result['error']:
96 for msg in result['error']:
97 sys.stderr.write('%s\n' % msg)
97 sys.stderr.write('%s\n' % msg)
98
98
99 if result['missing']:
99 if result['missing']:
100 sys.exit(2)
100 sys.exit(2)
101
101
102 if result['skipped'] or result['error']:
102 if result['skipped'] or result['error']:
103 sys.exit(1)
103 sys.exit(1)
104
104
105 def matchoutput(cmd, regexp, ignorestatus=False):
105 def matchoutput(cmd, regexp, ignorestatus=False):
106 """Return the match object if cmd executes successfully and its output
106 """Return the match object if cmd executes successfully and its output
107 is matched by the supplied regular expression.
107 is matched by the supplied regular expression.
108 """
108 """
109 r = re.compile(regexp)
109 r = re.compile(regexp)
110 try:
110 try:
111 p = subprocess.Popen(
111 p = subprocess.Popen(
112 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
112 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
113 except OSError as e:
113 except OSError as e:
114 if e.errno != errno.ENOENT:
114 if e.errno != errno.ENOENT:
115 raise
115 raise
116 ret = -1
116 ret = -1
117 s = p.communicate()[0]
117 s = p.communicate()[0]
118 ret = p.returncode
118 ret = p.returncode
119 return (ignorestatus or not ret) and r.search(s)
119 return (ignorestatus or not ret) and r.search(s)
120
120
121 @check("baz", "GNU Arch baz client")
121 @check("baz", "GNU Arch baz client")
122 def has_baz():
122 def has_baz():
123 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
123 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
124
124
125 @check("bzr", "Canonical's Bazaar client")
125 @check("bzr", "Canonical's Bazaar client")
126 def has_bzr():
126 def has_bzr():
127 try:
127 try:
128 import bzrlib
128 import bzrlib
129 import bzrlib.bzrdir
129 import bzrlib.bzrdir
130 import bzrlib.errors
130 import bzrlib.errors
131 import bzrlib.revision
131 import bzrlib.revision
132 import bzrlib.revisionspec
132 import bzrlib.revisionspec
133 bzrlib.revisionspec.RevisionSpec
133 bzrlib.revisionspec.RevisionSpec
134 return bzrlib.__doc__ is not None
134 return bzrlib.__doc__ is not None
135 except (AttributeError, ImportError):
135 except (AttributeError, ImportError):
136 return False
136 return False
137
137
138 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
138 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
139 def has_bzr_range(v):
139 def has_bzr_range(v):
140 major, minor = v.split('.')[0:2]
140 major, minor = v.split('.')[0:2]
141 try:
141 try:
142 import bzrlib
142 import bzrlib
143 return (bzrlib.__doc__ is not None
143 return (bzrlib.__doc__ is not None
144 and bzrlib.version_info[:2] >= (int(major), int(minor)))
144 and bzrlib.version_info[:2] >= (int(major), int(minor)))
145 except ImportError:
145 except ImportError:
146 return False
146 return False
147
147
148 @check("chg", "running with chg")
148 @check("chg", "running with chg")
149 def has_chg():
149 def has_chg():
150 return 'CHGHG' in os.environ
150 return 'CHGHG' in os.environ
151
151
152 @check("cvs", "cvs client/server")
152 @check("cvs", "cvs client/server")
153 def has_cvs():
153 def has_cvs():
154 re = br'Concurrent Versions System.*?server'
154 re = br'Concurrent Versions System.*?server'
155 return matchoutput('cvs --version 2>&1', re) and not has_msys()
155 return matchoutput('cvs --version 2>&1', re) and not has_msys()
156
156
157 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
157 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
158 def has_cvs112():
158 def has_cvs112():
159 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
159 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
160 return matchoutput('cvs --version 2>&1', re) and not has_msys()
160 return matchoutput('cvs --version 2>&1', re) and not has_msys()
161
161
162 @check("cvsnt", "cvsnt client/server")
162 @check("cvsnt", "cvsnt client/server")
163 def has_cvsnt():
163 def has_cvsnt():
164 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
164 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
165 return matchoutput('cvsnt --version 2>&1', re)
165 return matchoutput('cvsnt --version 2>&1', re)
166
166
167 @check("darcs", "darcs client")
167 @check("darcs", "darcs client")
168 def has_darcs():
168 def has_darcs():
169 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
169 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
170
170
171 @check("mtn", "monotone client (>= 1.0)")
171 @check("mtn", "monotone client (>= 1.0)")
172 def has_mtn():
172 def has_mtn():
173 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
173 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
174 'mtn --version', br'monotone 0\.', True)
174 'mtn --version', br'monotone 0\.', True)
175
175
176 @check("eol-in-paths", "end-of-lines in paths")
176 @check("eol-in-paths", "end-of-lines in paths")
177 def has_eol_in_paths():
177 def has_eol_in_paths():
178 try:
178 try:
179 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
179 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
180 os.close(fd)
180 os.close(fd)
181 os.remove(path)
181 os.remove(path)
182 return True
182 return True
183 except (IOError, OSError):
183 except (IOError, OSError):
184 return False
184 return False
185
185
186 @check("execbit", "executable bit")
186 @check("execbit", "executable bit")
187 def has_executablebit():
187 def has_executablebit():
188 try:
188 try:
189 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
189 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
190 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
190 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
191 try:
191 try:
192 os.close(fh)
192 os.close(fh)
193 m = os.stat(fn).st_mode & 0o777
193 m = os.stat(fn).st_mode & 0o777
194 new_file_has_exec = m & EXECFLAGS
194 new_file_has_exec = m & EXECFLAGS
195 os.chmod(fn, m ^ EXECFLAGS)
195 os.chmod(fn, m ^ EXECFLAGS)
196 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
196 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
197 finally:
197 finally:
198 os.unlink(fn)
198 os.unlink(fn)
199 except (IOError, OSError):
199 except (IOError, OSError):
200 # we don't care, the user probably won't be able to commit anyway
200 # we don't care, the user probably won't be able to commit anyway
201 return False
201 return False
202 return not (new_file_has_exec or exec_flags_cannot_flip)
202 return not (new_file_has_exec or exec_flags_cannot_flip)
203
203
204 @check("icasefs", "case insensitive file system")
204 @check("icasefs", "case insensitive file system")
205 def has_icasefs():
205 def has_icasefs():
206 # Stolen from mercurial.util
206 # Stolen from mercurial.util
207 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
207 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
208 os.close(fd)
208 os.close(fd)
209 try:
209 try:
210 s1 = os.stat(path)
210 s1 = os.stat(path)
211 d, b = os.path.split(path)
211 d, b = os.path.split(path)
212 p2 = os.path.join(d, b.upper())
212 p2 = os.path.join(d, b.upper())
213 if path == p2:
213 if path == p2:
214 p2 = os.path.join(d, b.lower())
214 p2 = os.path.join(d, b.lower())
215 try:
215 try:
216 s2 = os.stat(p2)
216 s2 = os.stat(p2)
217 return s2 == s1
217 return s2 == s1
218 except OSError:
218 except OSError:
219 return False
219 return False
220 finally:
220 finally:
221 os.remove(path)
221 os.remove(path)
222
222
223 @check("fifo", "named pipes")
223 @check("fifo", "named pipes")
224 def has_fifo():
224 def has_fifo():
225 if getattr(os, "mkfifo", None) is None:
225 if getattr(os, "mkfifo", None) is None:
226 return False
226 return False
227 name = tempfile.mktemp(dir='.', prefix=tempprefix)
227 name = tempfile.mktemp(dir='.', prefix=tempprefix)
228 try:
228 try:
229 os.mkfifo(name)
229 os.mkfifo(name)
230 os.unlink(name)
230 os.unlink(name)
231 return True
231 return True
232 except OSError:
232 except OSError:
233 return False
233 return False
234
234
235 @check("killdaemons", 'killdaemons.py support')
235 @check("killdaemons", 'killdaemons.py support')
236 def has_killdaemons():
236 def has_killdaemons():
237 return True
237 return True
238
238
239 @check("cacheable", "cacheable filesystem")
239 @check("cacheable", "cacheable filesystem")
240 def has_cacheable_fs():
240 def has_cacheable_fs():
241 from mercurial import util
241 from mercurial import util
242
242
243 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
243 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
244 os.close(fd)
244 os.close(fd)
245 try:
245 try:
246 return util.cachestat(path).cacheable()
246 return util.cachestat(path).cacheable()
247 finally:
247 finally:
248 os.remove(path)
248 os.remove(path)
249
249
250 @check("lsprof", "python lsprof module")
250 @check("lsprof", "python lsprof module")
251 def has_lsprof():
251 def has_lsprof():
252 try:
252 try:
253 import _lsprof
253 import _lsprof
254 _lsprof.Profiler # silence unused import warning
254 _lsprof.Profiler # silence unused import warning
255 return True
255 return True
256 except ImportError:
256 except ImportError:
257 return False
257 return False
258
258
259 def gethgversion():
259 def gethgversion():
260 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
260 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
261 if not m:
261 if not m:
262 return (0, 0)
262 return (0, 0)
263 return (int(m.group(1)), int(m.group(2)))
263 return (int(m.group(1)), int(m.group(2)))
264
264
265 @checkvers("hg", "Mercurial >= %s",
265 @checkvers("hg", "Mercurial >= %s",
266 list([(1.0 * x) / 10 for x in range(9, 99)]))
266 list([(1.0 * x) / 10 for x in range(9, 99)]))
267 def has_hg_range(v):
267 def has_hg_range(v):
268 major, minor = v.split('.')[0:2]
268 major, minor = v.split('.')[0:2]
269 return gethgversion() >= (int(major), int(minor))
269 return gethgversion() >= (int(major), int(minor))
270
270
271 @check("hg08", "Mercurial >= 0.8")
271 @check("hg08", "Mercurial >= 0.8")
272 def has_hg08():
272 def has_hg08():
273 if checks["hg09"][0]():
273 if checks["hg09"][0]():
274 return True
274 return True
275 return matchoutput('hg help annotate 2>&1', '--date')
275 return matchoutput('hg help annotate 2>&1', '--date')
276
276
277 @check("hg07", "Mercurial >= 0.7")
277 @check("hg07", "Mercurial >= 0.7")
278 def has_hg07():
278 def has_hg07():
279 if checks["hg08"][0]():
279 if checks["hg08"][0]():
280 return True
280 return True
281 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
281 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
282
282
283 @check("hg06", "Mercurial >= 0.6")
283 @check("hg06", "Mercurial >= 0.6")
284 def has_hg06():
284 def has_hg06():
285 if checks["hg07"][0]():
285 if checks["hg07"][0]():
286 return True
286 return True
287 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
287 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
288
288
289 @check("gettext", "GNU Gettext (msgfmt)")
289 @check("gettext", "GNU Gettext (msgfmt)")
290 def has_gettext():
290 def has_gettext():
291 return matchoutput('msgfmt --version', br'GNU gettext-tools')
291 return matchoutput('msgfmt --version', br'GNU gettext-tools')
292
292
293 @check("git", "git command line client")
293 @check("git", "git command line client")
294 def has_git():
294 def has_git():
295 return matchoutput('git --version 2>&1', br'^git version')
295 return matchoutput('git --version 2>&1', br'^git version')
296
296
297 def getgitversion():
297 def getgitversion():
298 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
298 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
299 if not m:
299 if not m:
300 return (0, 0)
300 return (0, 0)
301 return (int(m.group(1)), int(m.group(2)))
301 return (int(m.group(1)), int(m.group(2)))
302
302
303 # https://github.com/git-lfs/lfs-test-server
303 # https://github.com/git-lfs/lfs-test-server
304 @check("lfs-test-server", "git-lfs test server")
304 @check("lfs-test-server", "git-lfs test server")
305 def has_lfsserver():
305 def has_lfsserver():
306 exe = 'lfs-test-server'
306 exe = 'lfs-test-server'
307 if has_windows():
307 if has_windows():
308 exe = 'lfs-test-server.exe'
308 exe = 'lfs-test-server.exe'
309 return any(
309 return any(
310 os.access(os.path.join(path, exe), os.X_OK)
310 os.access(os.path.join(path, exe), os.X_OK)
311 for path in os.environ["PATH"].split(os.pathsep)
311 for path in os.environ["PATH"].split(os.pathsep)
312 )
312 )
313
313
314 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
314 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
315 def has_git_range(v):
315 def has_git_range(v):
316 major, minor = v.split('.')[0:2]
316 major, minor = v.split('.')[0:2]
317 return getgitversion() >= (int(major), int(minor))
317 return getgitversion() >= (int(major), int(minor))
318
318
319 @check("docutils", "Docutils text processing library")
319 @check("docutils", "Docutils text processing library")
320 def has_docutils():
320 def has_docutils():
321 try:
321 try:
322 import docutils.core
322 import docutils.core
323 docutils.core.publish_cmdline # silence unused import
323 docutils.core.publish_cmdline # silence unused import
324 return True
324 return True
325 except ImportError:
325 except ImportError:
326 return False
326 return False
327
327
328 def getsvnversion():
328 def getsvnversion():
329 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
329 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
330 if not m:
330 if not m:
331 return (0, 0)
331 return (0, 0)
332 return (int(m.group(1)), int(m.group(2)))
332 return (int(m.group(1)), int(m.group(2)))
333
333
334 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
334 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
335 def has_svn_range(v):
335 def has_svn_range(v):
336 major, minor = v.split('.')[0:2]
336 major, minor = v.split('.')[0:2]
337 return getsvnversion() >= (int(major), int(minor))
337 return getsvnversion() >= (int(major), int(minor))
338
338
339 @check("svn", "subversion client and admin tools")
339 @check("svn", "subversion client and admin tools")
340 def has_svn():
340 def has_svn():
341 return matchoutput('svn --version 2>&1', br'^svn, version') and \
341 return matchoutput('svn --version 2>&1', br'^svn, version') and \
342 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version')
342 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version')
343
343
344 @check("svn-bindings", "subversion python bindings")
344 @check("svn-bindings", "subversion python bindings")
345 def has_svn_bindings():
345 def has_svn_bindings():
346 try:
346 try:
347 import svn.core
347 import svn.core
348 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
348 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
349 if version < (1, 4):
349 if version < (1, 4):
350 return False
350 return False
351 return True
351 return True
352 except ImportError:
352 except ImportError:
353 return False
353 return False
354
354
355 @check("p4", "Perforce server and client")
355 @check("p4", "Perforce server and client")
356 def has_p4():
356 def has_p4():
357 return (matchoutput('p4 -V', br'Rev\. P4/') and
357 return (matchoutput('p4 -V', br'Rev\. P4/') and
358 matchoutput('p4d -V', br'Rev\. P4D/'))
358 matchoutput('p4d -V', br'Rev\. P4D/'))
359
359
360 @check("symlink", "symbolic links")
360 @check("symlink", "symbolic links")
361 def has_symlink():
361 def has_symlink():
362 if getattr(os, "symlink", None) is None:
362 if getattr(os, "symlink", None) is None:
363 return False
363 return False
364 name = tempfile.mktemp(dir='.', prefix=tempprefix)
364 name = tempfile.mktemp(dir='.', prefix=tempprefix)
365 try:
365 try:
366 os.symlink(".", name)
366 os.symlink(".", name)
367 os.unlink(name)
367 os.unlink(name)
368 return True
368 return True
369 except (OSError, AttributeError):
369 except (OSError, AttributeError):
370 return False
370 return False
371
371
372 @check("hardlink", "hardlinks")
372 @check("hardlink", "hardlinks")
373 def has_hardlink():
373 def has_hardlink():
374 from mercurial import util
374 from mercurial import util
375 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
375 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
376 os.close(fh)
376 os.close(fh)
377 name = tempfile.mktemp(dir='.', prefix=tempprefix)
377 name = tempfile.mktemp(dir='.', prefix=tempprefix)
378 try:
378 try:
379 util.oslink(_bytespath(fn), _bytespath(name))
379 util.oslink(_bytespath(fn), _bytespath(name))
380 os.unlink(name)
380 os.unlink(name)
381 return True
381 return True
382 except OSError:
382 except OSError:
383 return False
383 return False
384 finally:
384 finally:
385 os.unlink(fn)
385 os.unlink(fn)
386
386
387 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
387 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
388 def has_hardlink_whitelisted():
388 def has_hardlink_whitelisted():
389 from mercurial import util
389 from mercurial import util
390 try:
390 try:
391 fstype = util.getfstype(b'.')
391 fstype = util.getfstype(b'.')
392 except OSError:
392 except OSError:
393 return False
393 return False
394 return fstype in util._hardlinkfswhitelist
394 return fstype in util._hardlinkfswhitelist
395
395
396 @check("rmcwd", "can remove current working directory")
396 @check("rmcwd", "can remove current working directory")
397 def has_rmcwd():
397 def has_rmcwd():
398 ocwd = os.getcwd()
398 ocwd = os.getcwd()
399 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
399 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
400 try:
400 try:
401 os.chdir(temp)
401 os.chdir(temp)
402 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
402 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
403 # On Solaris and Windows, the cwd can't be removed by any names.
403 # On Solaris and Windows, the cwd can't be removed by any names.
404 os.rmdir(os.getcwd())
404 os.rmdir(os.getcwd())
405 return True
405 return True
406 except OSError:
406 except OSError:
407 return False
407 return False
408 finally:
408 finally:
409 os.chdir(ocwd)
409 os.chdir(ocwd)
410 # clean up temp dir on platforms where cwd can't be removed
410 # clean up temp dir on platforms where cwd can't be removed
411 try:
411 try:
412 os.rmdir(temp)
412 os.rmdir(temp)
413 except OSError:
413 except OSError:
414 pass
414 pass
415
415
416 @check("tla", "GNU Arch tla client")
416 @check("tla", "GNU Arch tla client")
417 def has_tla():
417 def has_tla():
418 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
418 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
419
419
420 @check("gpg", "gpg client")
420 @check("gpg", "gpg client")
421 def has_gpg():
421 def has_gpg():
422 return matchoutput('gpg --version 2>&1', br'GnuPG')
422 return matchoutput('gpg --version 2>&1', br'GnuPG')
423
423
424 @check("gpg2", "gpg client v2")
424 @check("gpg2", "gpg client v2")
425 def has_gpg2():
425 def has_gpg2():
426 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
426 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
427
427
428 @check("gpg21", "gpg client v2.1+")
428 @check("gpg21", "gpg client v2.1+")
429 def has_gpg21():
429 def has_gpg21():
430 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
430 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
431
431
432 @check("unix-permissions", "unix-style permissions")
432 @check("unix-permissions", "unix-style permissions")
433 def has_unix_permissions():
433 def has_unix_permissions():
434 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
434 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
435 try:
435 try:
436 fname = os.path.join(d, 'foo')
436 fname = os.path.join(d, 'foo')
437 for umask in (0o77, 0o07, 0o22):
437 for umask in (0o77, 0o07, 0o22):
438 os.umask(umask)
438 os.umask(umask)
439 f = open(fname, 'w')
439 f = open(fname, 'w')
440 f.close()
440 f.close()
441 mode = os.stat(fname).st_mode
441 mode = os.stat(fname).st_mode
442 os.unlink(fname)
442 os.unlink(fname)
443 if mode & 0o777 != ~umask & 0o666:
443 if mode & 0o777 != ~umask & 0o666:
444 return False
444 return False
445 return True
445 return True
446 finally:
446 finally:
447 os.rmdir(d)
447 os.rmdir(d)
448
448
449 @check("unix-socket", "AF_UNIX socket family")
449 @check("unix-socket", "AF_UNIX socket family")
450 def has_unix_socket():
450 def has_unix_socket():
451 return getattr(socket, 'AF_UNIX', None) is not None
451 return getattr(socket, 'AF_UNIX', None) is not None
452
452
453 @check("root", "root permissions")
453 @check("root", "root permissions")
454 def has_root():
454 def has_root():
455 return getattr(os, 'geteuid', None) and os.geteuid() == 0
455 return getattr(os, 'geteuid', None) and os.geteuid() == 0
456
456
457 @check("pyflakes", "Pyflakes python linter")
457 @check("pyflakes", "Pyflakes python linter")
458 def has_pyflakes():
458 def has_pyflakes():
459 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
459 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
460 br"<stdin>:1: 're' imported but unused",
460 br"<stdin>:1: 're' imported but unused",
461 True)
461 True)
462
462
463 @check("pylint", "Pylint python linter")
463 @check("pylint", "Pylint python linter")
464 def has_pylint():
464 def has_pylint():
465 return matchoutput("pylint --help",
465 return matchoutput("pylint --help",
466 br"Usage: pylint",
466 br"Usage: pylint",
467 True)
467 True)
468
468
469 @check("clang-format", "clang-format C code formatter")
469 @check("clang-format", "clang-format C code formatter")
470 def has_clang_format():
470 def has_clang_format():
471 m = matchoutput('clang-format --version', br'clang-format version (\d)')
471 m = matchoutput('clang-format --version', br'clang-format version (\d)')
472 # style changed somewhere between 4.x and 6.x
472 # style changed somewhere between 4.x and 6.x
473 return m and int(m.group(1)) >= 6
473 return m and int(m.group(1)) >= 6
474
474
475 @check("jshint", "JSHint static code analysis tool")
475 @check("jshint", "JSHint static code analysis tool")
476 def has_jshint():
476 def has_jshint():
477 return matchoutput("jshint --version 2>&1", br"jshint v")
477 return matchoutput("jshint --version 2>&1", br"jshint v")
478
478
479 @check("pygments", "Pygments source highlighting library")
479 @check("pygments", "Pygments source highlighting library")
480 def has_pygments():
480 def has_pygments():
481 try:
481 try:
482 import pygments
482 import pygments
483 pygments.highlight # silence unused import warning
483 pygments.highlight # silence unused import warning
484 return True
484 return True
485 except ImportError:
485 except ImportError:
486 return False
486 return False
487
487
488 @check("outer-repo", "outer repo")
488 @check("outer-repo", "outer repo")
489 def has_outer_repo():
489 def has_outer_repo():
490 # failing for other reasons than 'no repo' imply that there is a repo
490 # failing for other reasons than 'no repo' imply that there is a repo
491 return not matchoutput('hg root 2>&1',
491 return not matchoutput('hg root 2>&1',
492 br'abort: no repository found', True)
492 br'abort: no repository found', True)
493
493
494 @check("ssl", "ssl module available")
494 @check("ssl", "ssl module available")
495 def has_ssl():
495 def has_ssl():
496 try:
496 try:
497 import ssl
497 import ssl
498 ssl.CERT_NONE
498 ssl.CERT_NONE
499 return True
499 return True
500 except ImportError:
500 except ImportError:
501 return False
501 return False
502
502
503 @check("sslcontext", "python >= 2.7.9 ssl")
503 @check("sslcontext", "python >= 2.7.9 ssl")
504 def has_sslcontext():
504 def has_sslcontext():
505 try:
505 try:
506 import ssl
506 import ssl
507 ssl.SSLContext
507 ssl.SSLContext
508 return True
508 return True
509 except (ImportError, AttributeError):
509 except (ImportError, AttributeError):
510 return False
510 return False
511
511
512 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
512 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
513 def has_defaultcacerts():
513 def has_defaultcacerts():
514 from mercurial import sslutil, ui as uimod
514 from mercurial import sslutil, ui as uimod
515 ui = uimod.ui.load()
515 ui = uimod.ui.load()
516 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
516 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
517
517
518 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
518 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
519 def has_defaultcacertsloaded():
519 def has_defaultcacertsloaded():
520 import ssl
520 import ssl
521 from mercurial import sslutil, ui as uimod
521 from mercurial import sslutil, ui as uimod
522
522
523 if not has_defaultcacerts():
523 if not has_defaultcacerts():
524 return False
524 return False
525 if not has_sslcontext():
525 if not has_sslcontext():
526 return False
526 return False
527
527
528 ui = uimod.ui.load()
528 ui = uimod.ui.load()
529 cafile = sslutil._defaultcacerts(ui)
529 cafile = sslutil._defaultcacerts(ui)
530 ctx = ssl.create_default_context()
530 ctx = ssl.create_default_context()
531 if cafile:
531 if cafile:
532 ctx.load_verify_locations(cafile=cafile)
532 ctx.load_verify_locations(cafile=cafile)
533 else:
533 else:
534 ctx.load_default_certs()
534 ctx.load_default_certs()
535
535
536 return len(ctx.get_ca_certs()) > 0
536 return len(ctx.get_ca_certs()) > 0
537
537
538 @check("tls1.2", "TLS 1.2 protocol support")
538 @check("tls1.2", "TLS 1.2 protocol support")
539 def has_tls1_2():
539 def has_tls1_2():
540 from mercurial import sslutil
540 from mercurial import sslutil
541 return 'tls1.2' in sslutil.supportedprotocols
541 return 'tls1.2' in sslutil.supportedprotocols
542
542
543 @check("windows", "Windows")
543 @check("windows", "Windows")
544 def has_windows():
544 def has_windows():
545 return os.name == 'nt'
545 return os.name == 'nt'
546
546
547 @check("system-sh", "system() uses sh")
547 @check("system-sh", "system() uses sh")
548 def has_system_sh():
548 def has_system_sh():
549 return os.name != 'nt'
549 return os.name != 'nt'
550
550
551 @check("serve", "platform and python can manage 'hg serve -d'")
551 @check("serve", "platform and python can manage 'hg serve -d'")
552 def has_serve():
552 def has_serve():
553 return True
553 return True
554
554
555 @check("test-repo", "running tests from repository")
555 @check("test-repo", "running tests from repository")
556 def has_test_repo():
556 def has_test_repo():
557 t = os.environ["TESTDIR"]
557 t = os.environ["TESTDIR"]
558 return os.path.isdir(os.path.join(t, "..", ".hg"))
558 return os.path.isdir(os.path.join(t, "..", ".hg"))
559
559
560 @check("tic", "terminfo compiler and curses module")
560 @check("tic", "terminfo compiler and curses module")
561 def has_tic():
561 def has_tic():
562 try:
562 try:
563 import curses
563 import curses
564 curses.COLOR_BLUE
564 curses.COLOR_BLUE
565 return matchoutput('test -x "`which tic`"', br'')
565 return matchoutput('test -x "`which tic`"', br'')
566 except ImportError:
566 except ImportError:
567 return False
567 return False
568
568
569 @check("msys", "Windows with MSYS")
569 @check("msys", "Windows with MSYS")
570 def has_msys():
570 def has_msys():
571 return os.getenv('MSYSTEM')
571 return os.getenv('MSYSTEM')
572
572
573 @check("aix", "AIX")
573 @check("aix", "AIX")
574 def has_aix():
574 def has_aix():
575 return sys.platform.startswith("aix")
575 return sys.platform.startswith("aix")
576
576
577 @check("osx", "OS X")
577 @check("osx", "OS X")
578 def has_osx():
578 def has_osx():
579 return sys.platform == 'darwin'
579 return sys.platform == 'darwin'
580
580
581 @check("osxpackaging", "OS X packaging tools")
581 @check("osxpackaging", "OS X packaging tools")
582 def has_osxpackaging():
582 def has_osxpackaging():
583 try:
583 try:
584 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
584 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
585 and matchoutput(
585 and matchoutput(
586 'productbuild', br'Usage: productbuild ',
586 'productbuild', br'Usage: productbuild ',
587 ignorestatus=1)
587 ignorestatus=1)
588 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
588 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
589 and matchoutput(
589 and matchoutput(
590 'xar --help', br'Usage: xar', ignorestatus=1))
590 'xar --help', br'Usage: xar', ignorestatus=1))
591 except ImportError:
591 except ImportError:
592 return False
592 return False
593
593
594 @check('linuxormacos', 'Linux or MacOS')
594 @check('linuxormacos', 'Linux or MacOS')
595 def has_linuxormacos():
595 def has_linuxormacos():
596 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
596 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
597 return sys.platform.startswith(('linux', 'darwin'))
597 return sys.platform.startswith(('linux', 'darwin'))
598
598
599 @check("docker", "docker support")
599 @check("docker", "docker support")
600 def has_docker():
600 def has_docker():
601 pat = br'A self-sufficient runtime for'
601 pat = br'A self-sufficient runtime for'
602 if matchoutput('docker --help', pat):
602 if matchoutput('docker --help', pat):
603 if 'linux' not in sys.platform:
603 if 'linux' not in sys.platform:
604 # TODO: in theory we should be able to test docker-based
604 # TODO: in theory we should be able to test docker-based
605 # package creation on non-linux using boot2docker, but in
605 # package creation on non-linux using boot2docker, but in
606 # practice that requires extra coordination to make sure
606 # practice that requires extra coordination to make sure
607 # $TESTTEMP is going to be visible at the same path to the
607 # $TESTTEMP is going to be visible at the same path to the
608 # boot2docker VM. If we figure out how to verify that, we
608 # boot2docker VM. If we figure out how to verify that, we
609 # can use the following instead of just saying False:
609 # can use the following instead of just saying False:
610 # return 'DOCKER_HOST' in os.environ
610 # return 'DOCKER_HOST' in os.environ
611 return False
611 return False
612
612
613 return True
613 return True
614 return False
614 return False
615
615
616 @check("debhelper", "debian packaging tools")
616 @check("debhelper", "debian packaging tools")
617 def has_debhelper():
617 def has_debhelper():
618 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
618 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
619 # quote), so just accept anything in that spot.
619 # quote), so just accept anything in that spot.
620 dpkg = matchoutput('dpkg --version',
620 dpkg = matchoutput('dpkg --version',
621 br"Debian .dpkg' package management program")
621 br"Debian .dpkg' package management program")
622 dh = matchoutput('dh --help',
622 dh = matchoutput('dh --help',
623 br'dh is a part of debhelper.', ignorestatus=True)
623 br'dh is a part of debhelper.', ignorestatus=True)
624 dh_py2 = matchoutput('dh_python2 --help',
624 dh_py2 = matchoutput('dh_python2 --help',
625 br'other supported Python versions')
625 br'other supported Python versions')
626 # debuild comes from the 'devscripts' package, though you might want
626 # debuild comes from the 'devscripts' package, though you might want
627 # the 'build-debs' package instead, which has a dependency on devscripts.
627 # the 'build-debs' package instead, which has a dependency on devscripts.
628 debuild = matchoutput('debuild --help',
628 debuild = matchoutput('debuild --help',
629 br'to run debian/rules with given parameter')
629 br'to run debian/rules with given parameter')
630 return dpkg and dh and dh_py2 and debuild
630 return dpkg and dh and dh_py2 and debuild
631
631
632 @check("debdeps",
632 @check("debdeps",
633 "debian build dependencies (run dpkg-checkbuilddeps in contrib/)")
633 "debian build dependencies (run dpkg-checkbuilddeps in contrib/)")
634 def has_debdeps():
634 def has_debdeps():
635 # just check exit status (ignoring output)
635 # just check exit status (ignoring output)
636 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
636 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
637 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
637 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
638
638
639 @check("demandimport", "demandimport enabled")
639 @check("demandimport", "demandimport enabled")
640 def has_demandimport():
640 def has_demandimport():
641 # chg disables demandimport intentionally for performance wins.
641 # chg disables demandimport intentionally for performance wins.
642 return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable')
642 return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable')
643
643
644 @check("py3k", "running with Python 3.x")
644 @check("py3", "running with Python 3.x")
645 def has_py3k():
645 def has_py3():
646 return 3 == sys.version_info[0]
646 return 3 == sys.version_info[0]
647
647
648 @check("py3exe", "a Python 3.x interpreter is available")
648 @check("py3exe", "a Python 3.x interpreter is available")
649 def has_python3exe():
649 def has_python3exe():
650 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
650 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
651
651
652 @check("pure", "running with pure Python code")
652 @check("pure", "running with pure Python code")
653 def has_pure():
653 def has_pure():
654 return any([
654 return any([
655 os.environ.get("HGMODULEPOLICY") == "py",
655 os.environ.get("HGMODULEPOLICY") == "py",
656 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
656 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
657 ])
657 ])
658
658
659 @check("slow", "allow slow tests (use --allow-slow-tests)")
659 @check("slow", "allow slow tests (use --allow-slow-tests)")
660 def has_slow():
660 def has_slow():
661 return os.environ.get('HGTEST_SLOW') == 'slow'
661 return os.environ.get('HGTEST_SLOW') == 'slow'
662
662
663 @check("hypothesis", "Hypothesis automated test generation")
663 @check("hypothesis", "Hypothesis automated test generation")
664 def has_hypothesis():
664 def has_hypothesis():
665 try:
665 try:
666 import hypothesis
666 import hypothesis
667 hypothesis.given
667 hypothesis.given
668 return True
668 return True
669 except ImportError:
669 except ImportError:
670 return False
670 return False
671
671
672 @check("unziplinks", "unzip(1) understands and extracts symlinks")
672 @check("unziplinks", "unzip(1) understands and extracts symlinks")
673 def unzip_understands_symlinks():
673 def unzip_understands_symlinks():
674 return matchoutput('unzip --help', br'Info-ZIP')
674 return matchoutput('unzip --help', br'Info-ZIP')
675
675
676 @check("zstd", "zstd Python module available")
676 @check("zstd", "zstd Python module available")
677 def has_zstd():
677 def has_zstd():
678 try:
678 try:
679 import mercurial.zstd
679 import mercurial.zstd
680 mercurial.zstd.__version__
680 mercurial.zstd.__version__
681 return True
681 return True
682 except ImportError:
682 except ImportError:
683 return False
683 return False
684
684
685 @check("devfull", "/dev/full special file")
685 @check("devfull", "/dev/full special file")
686 def has_dev_full():
686 def has_dev_full():
687 return os.path.exists('/dev/full')
687 return os.path.exists('/dev/full')
688
688
689 @check("virtualenv", "Python virtualenv support")
689 @check("virtualenv", "Python virtualenv support")
690 def has_virtualenv():
690 def has_virtualenv():
691 try:
691 try:
692 import virtualenv
692 import virtualenv
693 virtualenv.ACTIVATE_SH
693 virtualenv.ACTIVATE_SH
694 return True
694 return True
695 except ImportError:
695 except ImportError:
696 return False
696 return False
697
697
698 @check("fsmonitor", "running tests with fsmonitor")
698 @check("fsmonitor", "running tests with fsmonitor")
699 def has_fsmonitor():
699 def has_fsmonitor():
700 return 'HGFSMONITOR_TESTS' in os.environ
700 return 'HGFSMONITOR_TESTS' in os.environ
701
701
702 @check("fuzzywuzzy", "Fuzzy string matching library")
702 @check("fuzzywuzzy", "Fuzzy string matching library")
703 def has_fuzzywuzzy():
703 def has_fuzzywuzzy():
704 try:
704 try:
705 import fuzzywuzzy
705 import fuzzywuzzy
706 fuzzywuzzy.__version__
706 fuzzywuzzy.__version__
707 return True
707 return True
708 except ImportError:
708 except ImportError:
709 return False
709 return False
710
710
711 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
711 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
712 def has_clang_libfuzzer():
712 def has_clang_libfuzzer():
713 mat = matchoutput('clang --version', b'clang version (\d)')
713 mat = matchoutput('clang --version', b'clang version (\d)')
714 if mat:
714 if mat:
715 # libfuzzer is new in clang 6
715 # libfuzzer is new in clang 6
716 return int(mat.group(1)) > 5
716 return int(mat.group(1)) > 5
717 return False
717 return False
718
718
719 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
719 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
720 def has_clang60():
720 def has_clang60():
721 return matchoutput('clang-6.0 --version', b'clang version 6\.')
721 return matchoutput('clang-6.0 --version', b'clang version 6\.')
722
722
723 @check("xdiff", "xdiff algorithm")
723 @check("xdiff", "xdiff algorithm")
724 def has_xdiff():
724 def has_xdiff():
725 try:
725 try:
726 from mercurial import policy
726 from mercurial import policy
727 bdiff = policy.importmod('bdiff')
727 bdiff = policy.importmod('bdiff')
728 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
728 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
729 except (ImportError, AttributeError):
729 except (ImportError, AttributeError):
730 return False
730 return False
731
731
732 @check('extraextensions', 'whether tests are running with extra extensions')
732 @check('extraextensions', 'whether tests are running with extra extensions')
733 def has_extraextensions():
733 def has_extraextensions():
734 return 'HGTESTEXTRAEXTENSIONS' in os.environ
734 return 'HGTESTEXTRAEXTENSIONS' in os.environ
735
735
736 def getrepofeatures():
736 def getrepofeatures():
737 """Obtain set of repository features in use.
737 """Obtain set of repository features in use.
738
738
739 HGREPOFEATURES can be used to define or remove features. It contains
739 HGREPOFEATURES can be used to define or remove features. It contains
740 a space-delimited list of feature strings. Strings beginning with ``-``
740 a space-delimited list of feature strings. Strings beginning with ``-``
741 mean to remove.
741 mean to remove.
742 """
742 """
743 # Default list provided by core.
743 # Default list provided by core.
744 features = {
744 features = {
745 'bundlerepo',
745 'bundlerepo',
746 'revlogstore',
746 'revlogstore',
747 'fncache',
747 'fncache',
748 }
748 }
749
749
750 # Features that imply other features.
750 # Features that imply other features.
751 implies = {
751 implies = {
752 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
752 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
753 }
753 }
754
754
755 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
755 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
756 if not override:
756 if not override:
757 continue
757 continue
758
758
759 if override.startswith('-'):
759 if override.startswith('-'):
760 if override[1:] in features:
760 if override[1:] in features:
761 features.remove(override[1:])
761 features.remove(override[1:])
762 else:
762 else:
763 features.add(override)
763 features.add(override)
764
764
765 for imply in implies.get(override, []):
765 for imply in implies.get(override, []):
766 if imply.startswith('-'):
766 if imply.startswith('-'):
767 if imply[1:] in features:
767 if imply[1:] in features:
768 features.remove(imply[1:])
768 features.remove(imply[1:])
769 else:
769 else:
770 features.add(imply)
770 features.add(imply)
771
771
772 return features
772 return features
773
773
774 @check('reporevlogstore', 'repository using the default revlog store')
774 @check('reporevlogstore', 'repository using the default revlog store')
775 def has_reporevlogstore():
775 def has_reporevlogstore():
776 return 'revlogstore' in getrepofeatures()
776 return 'revlogstore' in getrepofeatures()
777
777
778 @check('reposimplestore', 'repository using simple storage extension')
778 @check('reposimplestore', 'repository using simple storage extension')
779 def has_reposimplestore():
779 def has_reposimplestore():
780 return 'simplestore' in getrepofeatures()
780 return 'simplestore' in getrepofeatures()
781
781
782 @check('repobundlerepo', 'whether we can open bundle files as repos')
782 @check('repobundlerepo', 'whether we can open bundle files as repos')
783 def has_repobundlerepo():
783 def has_repobundlerepo():
784 return 'bundlerepo' in getrepofeatures()
784 return 'bundlerepo' in getrepofeatures()
785
785
786 @check('repofncache', 'repository has an fncache')
786 @check('repofncache', 'repository has an fncache')
787 def has_repofncache():
787 def has_repofncache():
788 return 'fncache' in getrepofeatures()
788 return 'fncache' in getrepofeatures()
789
789
790 @check('vcr', 'vcr http mocking library')
790 @check('vcr', 'vcr http mocking library')
791 def has_vcr():
791 def has_vcr():
792 try:
792 try:
793 import vcr
793 import vcr
794 vcr.VCR
794 vcr.VCR
795 return True
795 return True
796 except (ImportError, AttributeError):
796 except (ImportError, AttributeError):
797 pass
797 pass
798 return False
798 return False
@@ -1,3240 +1,3240 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 # 10) parallel, pure, tests that call run-tests:
38 # 10) parallel, pure, tests that call run-tests:
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 #
40 #
41 # (You could use any subset of the tests: test-s* happens to match
41 # (You could use any subset of the tests: test-s* happens to match
42 # enough that it's worth doing parallel runs, few enough that it
42 # enough that it's worth doing parallel runs, few enough that it
43 # completes fairly quickly, includes both shell and Python scripts, and
43 # completes fairly quickly, includes both shell and Python scripts, and
44 # includes some scripts that run daemon processes.)
44 # includes some scripts that run daemon processes.)
45
45
46 from __future__ import absolute_import, print_function
46 from __future__ import absolute_import, print_function
47
47
48 import argparse
48 import argparse
49 import collections
49 import collections
50 import difflib
50 import difflib
51 import distutils.version as version
51 import distutils.version as version
52 import errno
52 import errno
53 import json
53 import json
54 import multiprocessing
54 import multiprocessing
55 import os
55 import os
56 import random
56 import random
57 import re
57 import re
58 import shutil
58 import shutil
59 import signal
59 import signal
60 import socket
60 import socket
61 import subprocess
61 import subprocess
62 import sys
62 import sys
63 import sysconfig
63 import sysconfig
64 import tempfile
64 import tempfile
65 import threading
65 import threading
66 import time
66 import time
67 import unittest
67 import unittest
68 import uuid
68 import uuid
69 import xml.dom.minidom as minidom
69 import xml.dom.minidom as minidom
70
70
71 try:
71 try:
72 import Queue as queue
72 import Queue as queue
73 except ImportError:
73 except ImportError:
74 import queue
74 import queue
75
75
76 try:
76 try:
77 import shlex
77 import shlex
78 shellquote = shlex.quote
78 shellquote = shlex.quote
79 except (ImportError, AttributeError):
79 except (ImportError, AttributeError):
80 import pipes
80 import pipes
81 shellquote = pipes.quote
81 shellquote = pipes.quote
82
82
83 if os.environ.get('RTUNICODEPEDANTRY', False):
83 if os.environ.get('RTUNICODEPEDANTRY', False):
84 try:
84 try:
85 reload(sys)
85 reload(sys)
86 sys.setdefaultencoding("undefined")
86 sys.setdefaultencoding("undefined")
87 except NameError:
87 except NameError:
88 pass
88 pass
89
89
90 processlock = threading.Lock()
90 processlock = threading.Lock()
91
91
92 pygmentspresent = False
92 pygmentspresent = False
93 # ANSI color is unsupported prior to Windows 10
93 # ANSI color is unsupported prior to Windows 10
94 if os.name != 'nt':
94 if os.name != 'nt':
95 try: # is pygments installed
95 try: # is pygments installed
96 import pygments
96 import pygments
97 import pygments.lexers as lexers
97 import pygments.lexers as lexers
98 import pygments.lexer as lexer
98 import pygments.lexer as lexer
99 import pygments.formatters as formatters
99 import pygments.formatters as formatters
100 import pygments.token as token
100 import pygments.token as token
101 import pygments.style as style
101 import pygments.style as style
102 pygmentspresent = True
102 pygmentspresent = True
103 difflexer = lexers.DiffLexer()
103 difflexer = lexers.DiffLexer()
104 terminal256formatter = formatters.Terminal256Formatter()
104 terminal256formatter = formatters.Terminal256Formatter()
105 except ImportError:
105 except ImportError:
106 pass
106 pass
107
107
108 if pygmentspresent:
108 if pygmentspresent:
109 class TestRunnerStyle(style.Style):
109 class TestRunnerStyle(style.Style):
110 default_style = ""
110 default_style = ""
111 skipped = token.string_to_tokentype("Token.Generic.Skipped")
111 skipped = token.string_to_tokentype("Token.Generic.Skipped")
112 failed = token.string_to_tokentype("Token.Generic.Failed")
112 failed = token.string_to_tokentype("Token.Generic.Failed")
113 skippedname = token.string_to_tokentype("Token.Generic.SName")
113 skippedname = token.string_to_tokentype("Token.Generic.SName")
114 failedname = token.string_to_tokentype("Token.Generic.FName")
114 failedname = token.string_to_tokentype("Token.Generic.FName")
115 styles = {
115 styles = {
116 skipped: '#e5e5e5',
116 skipped: '#e5e5e5',
117 skippedname: '#00ffff',
117 skippedname: '#00ffff',
118 failed: '#7f0000',
118 failed: '#7f0000',
119 failedname: '#ff0000',
119 failedname: '#ff0000',
120 }
120 }
121
121
122 class TestRunnerLexer(lexer.RegexLexer):
122 class TestRunnerLexer(lexer.RegexLexer):
123 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
123 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
124 tokens = {
124 tokens = {
125 'root': [
125 'root': [
126 (r'^Skipped', token.Generic.Skipped, 'skipped'),
126 (r'^Skipped', token.Generic.Skipped, 'skipped'),
127 (r'^Failed ', token.Generic.Failed, 'failed'),
127 (r'^Failed ', token.Generic.Failed, 'failed'),
128 (r'^ERROR: ', token.Generic.Failed, 'failed'),
128 (r'^ERROR: ', token.Generic.Failed, 'failed'),
129 ],
129 ],
130 'skipped': [
130 'skipped': [
131 (testpattern, token.Generic.SName),
131 (testpattern, token.Generic.SName),
132 (r':.*', token.Generic.Skipped),
132 (r':.*', token.Generic.Skipped),
133 ],
133 ],
134 'failed': [
134 'failed': [
135 (testpattern, token.Generic.FName),
135 (testpattern, token.Generic.FName),
136 (r'(:| ).*', token.Generic.Failed),
136 (r'(:| ).*', token.Generic.Failed),
137 ]
137 ]
138 }
138 }
139
139
140 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
140 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
141 runnerlexer = TestRunnerLexer()
141 runnerlexer = TestRunnerLexer()
142
142
143 origenviron = os.environ.copy()
143 origenviron = os.environ.copy()
144
144
145 if sys.version_info > (3, 5, 0):
145 if sys.version_info > (3, 5, 0):
146 PYTHON3 = True
146 PYTHON3 = True
147 xrange = range # we use xrange in one place, and we'd rather not use range
147 xrange = range # we use xrange in one place, and we'd rather not use range
148 def _bytespath(p):
148 def _bytespath(p):
149 if p is None:
149 if p is None:
150 return p
150 return p
151 return p.encode('utf-8')
151 return p.encode('utf-8')
152
152
153 def _strpath(p):
153 def _strpath(p):
154 if p is None:
154 if p is None:
155 return p
155 return p
156 return p.decode('utf-8')
156 return p.decode('utf-8')
157
157
158 osenvironb = getattr(os, 'environb', None)
158 osenvironb = getattr(os, 'environb', None)
159 if osenvironb is None:
159 if osenvironb is None:
160 # Windows lacks os.environb, for instance. A proxy over the real thing
160 # Windows lacks os.environb, for instance. A proxy over the real thing
161 # instead of a copy allows the environment to be updated via bytes on
161 # instead of a copy allows the environment to be updated via bytes on
162 # all platforms.
162 # all platforms.
163 class environbytes(object):
163 class environbytes(object):
164 def __init__(self, strenv):
164 def __init__(self, strenv):
165 self.__len__ = strenv.__len__
165 self.__len__ = strenv.__len__
166 self.clear = strenv.clear
166 self.clear = strenv.clear
167 self._strenv = strenv
167 self._strenv = strenv
168 def __getitem__(self, k):
168 def __getitem__(self, k):
169 v = self._strenv.__getitem__(_strpath(k))
169 v = self._strenv.__getitem__(_strpath(k))
170 return _bytespath(v)
170 return _bytespath(v)
171 def __setitem__(self, k, v):
171 def __setitem__(self, k, v):
172 self._strenv.__setitem__(_strpath(k), _strpath(v))
172 self._strenv.__setitem__(_strpath(k), _strpath(v))
173 def __delitem__(self, k):
173 def __delitem__(self, k):
174 self._strenv.__delitem__(_strpath(k))
174 self._strenv.__delitem__(_strpath(k))
175 def __contains__(self, k):
175 def __contains__(self, k):
176 return self._strenv.__contains__(_strpath(k))
176 return self._strenv.__contains__(_strpath(k))
177 def __iter__(self):
177 def __iter__(self):
178 return iter([_bytespath(k) for k in iter(self._strenv)])
178 return iter([_bytespath(k) for k in iter(self._strenv)])
179 def get(self, k, default=None):
179 def get(self, k, default=None):
180 v = self._strenv.get(_strpath(k), _strpath(default))
180 v = self._strenv.get(_strpath(k), _strpath(default))
181 return _bytespath(v)
181 return _bytespath(v)
182 def pop(self, k, default=None):
182 def pop(self, k, default=None):
183 v = self._strenv.pop(_strpath(k), _strpath(default))
183 v = self._strenv.pop(_strpath(k), _strpath(default))
184 return _bytespath(v)
184 return _bytespath(v)
185
185
186 osenvironb = environbytes(os.environ)
186 osenvironb = environbytes(os.environ)
187
187
188 getcwdb = getattr(os, 'getcwdb')
188 getcwdb = getattr(os, 'getcwdb')
189 if not getcwdb or os.name == 'nt':
189 if not getcwdb or os.name == 'nt':
190 getcwdb = lambda: _bytespath(os.getcwd())
190 getcwdb = lambda: _bytespath(os.getcwd())
191
191
192 elif sys.version_info >= (3, 0, 0):
192 elif sys.version_info >= (3, 0, 0):
193 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
193 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
194 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
194 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
195 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
195 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
196 else:
196 else:
197 PYTHON3 = False
197 PYTHON3 = False
198
198
199 # In python 2.x, path operations are generally done using
199 # In python 2.x, path operations are generally done using
200 # bytestrings by default, so we don't have to do any extra
200 # bytestrings by default, so we don't have to do any extra
201 # fiddling there. We define the wrapper functions anyway just to
201 # fiddling there. We define the wrapper functions anyway just to
202 # help keep code consistent between platforms.
202 # help keep code consistent between platforms.
203 def _bytespath(p):
203 def _bytespath(p):
204 return p
204 return p
205
205
206 _strpath = _bytespath
206 _strpath = _bytespath
207 osenvironb = os.environ
207 osenvironb = os.environ
208 getcwdb = os.getcwd
208 getcwdb = os.getcwd
209
209
210 # For Windows support
210 # For Windows support
211 wifexited = getattr(os, "WIFEXITED", lambda x: False)
211 wifexited = getattr(os, "WIFEXITED", lambda x: False)
212
212
213 # Whether to use IPv6
213 # Whether to use IPv6
214 def checksocketfamily(name, port=20058):
214 def checksocketfamily(name, port=20058):
215 """return true if we can listen on localhost using family=name
215 """return true if we can listen on localhost using family=name
216
216
217 name should be either 'AF_INET', or 'AF_INET6'.
217 name should be either 'AF_INET', or 'AF_INET6'.
218 port being used is okay - EADDRINUSE is considered as successful.
218 port being used is okay - EADDRINUSE is considered as successful.
219 """
219 """
220 family = getattr(socket, name, None)
220 family = getattr(socket, name, None)
221 if family is None:
221 if family is None:
222 return False
222 return False
223 try:
223 try:
224 s = socket.socket(family, socket.SOCK_STREAM)
224 s = socket.socket(family, socket.SOCK_STREAM)
225 s.bind(('localhost', port))
225 s.bind(('localhost', port))
226 s.close()
226 s.close()
227 return True
227 return True
228 except socket.error as exc:
228 except socket.error as exc:
229 if exc.errno == errno.EADDRINUSE:
229 if exc.errno == errno.EADDRINUSE:
230 return True
230 return True
231 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
231 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
232 return False
232 return False
233 else:
233 else:
234 raise
234 raise
235 else:
235 else:
236 return False
236 return False
237
237
238 # useipv6 will be set by parseargs
238 # useipv6 will be set by parseargs
239 useipv6 = None
239 useipv6 = None
240
240
241 def checkportisavailable(port):
241 def checkportisavailable(port):
242 """return true if a port seems free to bind on localhost"""
242 """return true if a port seems free to bind on localhost"""
243 if useipv6:
243 if useipv6:
244 family = socket.AF_INET6
244 family = socket.AF_INET6
245 else:
245 else:
246 family = socket.AF_INET
246 family = socket.AF_INET
247 try:
247 try:
248 s = socket.socket(family, socket.SOCK_STREAM)
248 s = socket.socket(family, socket.SOCK_STREAM)
249 s.bind(('localhost', port))
249 s.bind(('localhost', port))
250 s.close()
250 s.close()
251 return True
251 return True
252 except socket.error as exc:
252 except socket.error as exc:
253 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
253 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
254 errno.EPROTONOSUPPORT):
254 errno.EPROTONOSUPPORT):
255 raise
255 raise
256 return False
256 return False
257
257
258 closefds = os.name == 'posix'
258 closefds = os.name == 'posix'
259 def Popen4(cmd, wd, timeout, env=None):
259 def Popen4(cmd, wd, timeout, env=None):
260 processlock.acquire()
260 processlock.acquire()
261 p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1,
261 p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1,
262 cwd=_strpath(wd), env=env,
262 cwd=_strpath(wd), env=env,
263 close_fds=closefds,
263 close_fds=closefds,
264 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
264 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
265 stderr=subprocess.STDOUT)
265 stderr=subprocess.STDOUT)
266 processlock.release()
266 processlock.release()
267
267
268 p.fromchild = p.stdout
268 p.fromchild = p.stdout
269 p.tochild = p.stdin
269 p.tochild = p.stdin
270 p.childerr = p.stderr
270 p.childerr = p.stderr
271
271
272 p.timeout = False
272 p.timeout = False
273 if timeout:
273 if timeout:
274 def t():
274 def t():
275 start = time.time()
275 start = time.time()
276 while time.time() - start < timeout and p.returncode is None:
276 while time.time() - start < timeout and p.returncode is None:
277 time.sleep(.1)
277 time.sleep(.1)
278 p.timeout = True
278 p.timeout = True
279 if p.returncode is None:
279 if p.returncode is None:
280 terminate(p)
280 terminate(p)
281 threading.Thread(target=t).start()
281 threading.Thread(target=t).start()
282
282
283 return p
283 return p
284
284
285 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
285 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
286 IMPL_PATH = b'PYTHONPATH'
286 IMPL_PATH = b'PYTHONPATH'
287 if 'java' in sys.platform:
287 if 'java' in sys.platform:
288 IMPL_PATH = b'JYTHONPATH'
288 IMPL_PATH = b'JYTHONPATH'
289
289
290 defaults = {
290 defaults = {
291 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
291 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
292 'timeout': ('HGTEST_TIMEOUT', 180),
292 'timeout': ('HGTEST_TIMEOUT', 180),
293 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
293 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
294 'port': ('HGTEST_PORT', 20059),
294 'port': ('HGTEST_PORT', 20059),
295 'shell': ('HGTEST_SHELL', 'sh'),
295 'shell': ('HGTEST_SHELL', 'sh'),
296 }
296 }
297
297
298 def canonpath(path):
298 def canonpath(path):
299 return os.path.realpath(os.path.expanduser(path))
299 return os.path.realpath(os.path.expanduser(path))
300
300
301 def parselistfiles(files, listtype, warn=True):
301 def parselistfiles(files, listtype, warn=True):
302 entries = dict()
302 entries = dict()
303 for filename in files:
303 for filename in files:
304 try:
304 try:
305 path = os.path.expanduser(os.path.expandvars(filename))
305 path = os.path.expanduser(os.path.expandvars(filename))
306 f = open(path, "rb")
306 f = open(path, "rb")
307 except IOError as err:
307 except IOError as err:
308 if err.errno != errno.ENOENT:
308 if err.errno != errno.ENOENT:
309 raise
309 raise
310 if warn:
310 if warn:
311 print("warning: no such %s file: %s" % (listtype, filename))
311 print("warning: no such %s file: %s" % (listtype, filename))
312 continue
312 continue
313
313
314 for line in f.readlines():
314 for line in f.readlines():
315 line = line.split(b'#', 1)[0].strip()
315 line = line.split(b'#', 1)[0].strip()
316 if line:
316 if line:
317 entries[line] = filename
317 entries[line] = filename
318
318
319 f.close()
319 f.close()
320 return entries
320 return entries
321
321
322 def parsettestcases(path):
322 def parsettestcases(path):
323 """read a .t test file, return a set of test case names
323 """read a .t test file, return a set of test case names
324
324
325 If path does not exist, return an empty set.
325 If path does not exist, return an empty set.
326 """
326 """
327 cases = []
327 cases = []
328 try:
328 try:
329 with open(path, 'rb') as f:
329 with open(path, 'rb') as f:
330 for l in f:
330 for l in f:
331 if l.startswith(b'#testcases '):
331 if l.startswith(b'#testcases '):
332 cases.append(sorted(l[11:].split()))
332 cases.append(sorted(l[11:].split()))
333 except IOError as ex:
333 except IOError as ex:
334 if ex.errno != errno.ENOENT:
334 if ex.errno != errno.ENOENT:
335 raise
335 raise
336 return cases
336 return cases
337
337
338 def getparser():
338 def getparser():
339 """Obtain the OptionParser used by the CLI."""
339 """Obtain the OptionParser used by the CLI."""
340 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
340 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
341
341
342 selection = parser.add_argument_group('Test Selection')
342 selection = parser.add_argument_group('Test Selection')
343 selection.add_argument('--allow-slow-tests', action='store_true',
343 selection.add_argument('--allow-slow-tests', action='store_true',
344 help='allow extremely slow tests')
344 help='allow extremely slow tests')
345 selection.add_argument("--blacklist", action="append",
345 selection.add_argument("--blacklist", action="append",
346 help="skip tests listed in the specified blacklist file")
346 help="skip tests listed in the specified blacklist file")
347 selection.add_argument("--changed",
347 selection.add_argument("--changed",
348 help="run tests that are changed in parent rev or working directory")
348 help="run tests that are changed in parent rev or working directory")
349 selection.add_argument("-k", "--keywords",
349 selection.add_argument("-k", "--keywords",
350 help="run tests matching keywords")
350 help="run tests matching keywords")
351 selection.add_argument("-r", "--retest", action="store_true",
351 selection.add_argument("-r", "--retest", action="store_true",
352 help = "retest failed tests")
352 help = "retest failed tests")
353 selection.add_argument("--test-list", action="append",
353 selection.add_argument("--test-list", action="append",
354 help="read tests to run from the specified file")
354 help="read tests to run from the specified file")
355 selection.add_argument("--whitelist", action="append",
355 selection.add_argument("--whitelist", action="append",
356 help="always run tests listed in the specified whitelist file")
356 help="always run tests listed in the specified whitelist file")
357 selection.add_argument('tests', metavar='TESTS', nargs='*',
357 selection.add_argument('tests', metavar='TESTS', nargs='*',
358 help='Tests to run')
358 help='Tests to run')
359
359
360 harness = parser.add_argument_group('Test Harness Behavior')
360 harness = parser.add_argument_group('Test Harness Behavior')
361 harness.add_argument('--bisect-repo',
361 harness.add_argument('--bisect-repo',
362 metavar='bisect_repo',
362 metavar='bisect_repo',
363 help=("Path of a repo to bisect. Use together with "
363 help=("Path of a repo to bisect. Use together with "
364 "--known-good-rev"))
364 "--known-good-rev"))
365 harness.add_argument("-d", "--debug", action="store_true",
365 harness.add_argument("-d", "--debug", action="store_true",
366 help="debug mode: write output of test scripts to console"
366 help="debug mode: write output of test scripts to console"
367 " rather than capturing and diffing it (disables timeout)")
367 " rather than capturing and diffing it (disables timeout)")
368 harness.add_argument("-f", "--first", action="store_true",
368 harness.add_argument("-f", "--first", action="store_true",
369 help="exit on the first test failure")
369 help="exit on the first test failure")
370 harness.add_argument("-i", "--interactive", action="store_true",
370 harness.add_argument("-i", "--interactive", action="store_true",
371 help="prompt to accept changed output")
371 help="prompt to accept changed output")
372 harness.add_argument("-j", "--jobs", type=int,
372 harness.add_argument("-j", "--jobs", type=int,
373 help="number of jobs to run in parallel"
373 help="number of jobs to run in parallel"
374 " (default: $%s or %d)" % defaults['jobs'])
374 " (default: $%s or %d)" % defaults['jobs'])
375 harness.add_argument("--keep-tmpdir", action="store_true",
375 harness.add_argument("--keep-tmpdir", action="store_true",
376 help="keep temporary directory after running tests")
376 help="keep temporary directory after running tests")
377 harness.add_argument('--known-good-rev',
377 harness.add_argument('--known-good-rev',
378 metavar="known_good_rev",
378 metavar="known_good_rev",
379 help=("Automatically bisect any failures using this "
379 help=("Automatically bisect any failures using this "
380 "revision as a known-good revision."))
380 "revision as a known-good revision."))
381 harness.add_argument("--list-tests", action="store_true",
381 harness.add_argument("--list-tests", action="store_true",
382 help="list tests instead of running them")
382 help="list tests instead of running them")
383 harness.add_argument("--loop", action="store_true",
383 harness.add_argument("--loop", action="store_true",
384 help="loop tests repeatedly")
384 help="loop tests repeatedly")
385 harness.add_argument('--random', action="store_true",
385 harness.add_argument('--random', action="store_true",
386 help='run tests in random order')
386 help='run tests in random order')
387 harness.add_argument('--order-by-runtime', action="store_true",
387 harness.add_argument('--order-by-runtime', action="store_true",
388 help='run slowest tests first, according to .testtimes')
388 help='run slowest tests first, according to .testtimes')
389 harness.add_argument("-p", "--port", type=int,
389 harness.add_argument("-p", "--port", type=int,
390 help="port on which servers should listen"
390 help="port on which servers should listen"
391 " (default: $%s or %d)" % defaults['port'])
391 " (default: $%s or %d)" % defaults['port'])
392 harness.add_argument('--profile-runner', action='store_true',
392 harness.add_argument('--profile-runner', action='store_true',
393 help='run statprof on run-tests')
393 help='run statprof on run-tests')
394 harness.add_argument("-R", "--restart", action="store_true",
394 harness.add_argument("-R", "--restart", action="store_true",
395 help="restart at last error")
395 help="restart at last error")
396 harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
396 harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
397 help="run each test N times (default=1)", default=1)
397 help="run each test N times (default=1)", default=1)
398 harness.add_argument("--shell",
398 harness.add_argument("--shell",
399 help="shell to use (default: $%s or %s)" % defaults['shell'])
399 help="shell to use (default: $%s or %s)" % defaults['shell'])
400 harness.add_argument('--showchannels', action='store_true',
400 harness.add_argument('--showchannels', action='store_true',
401 help='show scheduling channels')
401 help='show scheduling channels')
402 harness.add_argument("--slowtimeout", type=int,
402 harness.add_argument("--slowtimeout", type=int,
403 help="kill errant slow tests after SLOWTIMEOUT seconds"
403 help="kill errant slow tests after SLOWTIMEOUT seconds"
404 " (default: $%s or %d)" % defaults['slowtimeout'])
404 " (default: $%s or %d)" % defaults['slowtimeout'])
405 harness.add_argument("-t", "--timeout", type=int,
405 harness.add_argument("-t", "--timeout", type=int,
406 help="kill errant tests after TIMEOUT seconds"
406 help="kill errant tests after TIMEOUT seconds"
407 " (default: $%s or %d)" % defaults['timeout'])
407 " (default: $%s or %d)" % defaults['timeout'])
408 harness.add_argument("--tmpdir",
408 harness.add_argument("--tmpdir",
409 help="run tests in the given temporary directory"
409 help="run tests in the given temporary directory"
410 " (implies --keep-tmpdir)")
410 " (implies --keep-tmpdir)")
411 harness.add_argument("-v", "--verbose", action="store_true",
411 harness.add_argument("-v", "--verbose", action="store_true",
412 help="output verbose messages")
412 help="output verbose messages")
413
413
414 hgconf = parser.add_argument_group('Mercurial Configuration')
414 hgconf = parser.add_argument_group('Mercurial Configuration')
415 hgconf.add_argument("--chg", action="store_true",
415 hgconf.add_argument("--chg", action="store_true",
416 help="install and use chg wrapper in place of hg")
416 help="install and use chg wrapper in place of hg")
417 hgconf.add_argument("--compiler",
417 hgconf.add_argument("--compiler",
418 help="compiler to build with")
418 help="compiler to build with")
419 hgconf.add_argument('--extra-config-opt', action="append", default=[],
419 hgconf.add_argument('--extra-config-opt', action="append", default=[],
420 help='set the given config opt in the test hgrc')
420 help='set the given config opt in the test hgrc')
421 hgconf.add_argument("-l", "--local", action="store_true",
421 hgconf.add_argument("-l", "--local", action="store_true",
422 help="shortcut for --with-hg=<testdir>/../hg, "
422 help="shortcut for --with-hg=<testdir>/../hg, "
423 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
423 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
424 hgconf.add_argument("--ipv6", action="store_true",
424 hgconf.add_argument("--ipv6", action="store_true",
425 help="prefer IPv6 to IPv4 for network related tests")
425 help="prefer IPv6 to IPv4 for network related tests")
426 hgconf.add_argument("--pure", action="store_true",
426 hgconf.add_argument("--pure", action="store_true",
427 help="use pure Python code instead of C extensions")
427 help="use pure Python code instead of C extensions")
428 hgconf.add_argument("-3", "--py3k-warnings", action="store_true",
428 hgconf.add_argument("-3", "--py3-warnings", action="store_true",
429 help="enable Py3k warnings on Python 2.7+")
429 help="enable Py3k warnings on Python 2.7+")
430 hgconf.add_argument("--with-chg", metavar="CHG",
430 hgconf.add_argument("--with-chg", metavar="CHG",
431 help="use specified chg wrapper in place of hg")
431 help="use specified chg wrapper in place of hg")
432 hgconf.add_argument("--with-hg",
432 hgconf.add_argument("--with-hg",
433 metavar="HG",
433 metavar="HG",
434 help="test using specified hg script rather than a "
434 help="test using specified hg script rather than a "
435 "temporary installation")
435 "temporary installation")
436
436
437 reporting = parser.add_argument_group('Results Reporting')
437 reporting = parser.add_argument_group('Results Reporting')
438 reporting.add_argument("-C", "--annotate", action="store_true",
438 reporting.add_argument("-C", "--annotate", action="store_true",
439 help="output files annotated with coverage")
439 help="output files annotated with coverage")
440 reporting.add_argument("--color", choices=["always", "auto", "never"],
440 reporting.add_argument("--color", choices=["always", "auto", "never"],
441 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
441 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
442 help="colorisation: always|auto|never (default: auto)")
442 help="colorisation: always|auto|never (default: auto)")
443 reporting.add_argument("-c", "--cover", action="store_true",
443 reporting.add_argument("-c", "--cover", action="store_true",
444 help="print a test coverage report")
444 help="print a test coverage report")
445 reporting.add_argument('--exceptions', action='store_true',
445 reporting.add_argument('--exceptions', action='store_true',
446 help='log all exceptions and generate an exception report')
446 help='log all exceptions and generate an exception report')
447 reporting.add_argument("-H", "--htmlcov", action="store_true",
447 reporting.add_argument("-H", "--htmlcov", action="store_true",
448 help="create an HTML report of the coverage of the files")
448 help="create an HTML report of the coverage of the files")
449 reporting.add_argument("--json", action="store_true",
449 reporting.add_argument("--json", action="store_true",
450 help="store test result data in 'report.json' file")
450 help="store test result data in 'report.json' file")
451 reporting.add_argument("--outputdir",
451 reporting.add_argument("--outputdir",
452 help="directory to write error logs to (default=test directory)")
452 help="directory to write error logs to (default=test directory)")
453 reporting.add_argument("-n", "--nodiff", action="store_true",
453 reporting.add_argument("-n", "--nodiff", action="store_true",
454 help="skip showing test changes")
454 help="skip showing test changes")
455 reporting.add_argument("-S", "--noskips", action="store_true",
455 reporting.add_argument("-S", "--noskips", action="store_true",
456 help="don't report skip tests verbosely")
456 help="don't report skip tests verbosely")
457 reporting.add_argument("--time", action="store_true",
457 reporting.add_argument("--time", action="store_true",
458 help="time how long each test takes")
458 help="time how long each test takes")
459 reporting.add_argument("--view",
459 reporting.add_argument("--view",
460 help="external diff viewer")
460 help="external diff viewer")
461 reporting.add_argument("--xunit",
461 reporting.add_argument("--xunit",
462 help="record xunit results at specified path")
462 help="record xunit results at specified path")
463
463
464 for option, (envvar, default) in defaults.items():
464 for option, (envvar, default) in defaults.items():
465 defaults[option] = type(default)(os.environ.get(envvar, default))
465 defaults[option] = type(default)(os.environ.get(envvar, default))
466 parser.set_defaults(**defaults)
466 parser.set_defaults(**defaults)
467
467
468 return parser
468 return parser
469
469
470 def parseargs(args, parser):
470 def parseargs(args, parser):
471 """Parse arguments with our OptionParser and validate results."""
471 """Parse arguments with our OptionParser and validate results."""
472 options = parser.parse_args(args)
472 options = parser.parse_args(args)
473
473
474 # jython is always pure
474 # jython is always pure
475 if 'java' in sys.platform or '__pypy__' in sys.modules:
475 if 'java' in sys.platform or '__pypy__' in sys.modules:
476 options.pure = True
476 options.pure = True
477
477
478 if options.with_hg:
478 if options.with_hg:
479 options.with_hg = canonpath(_bytespath(options.with_hg))
479 options.with_hg = canonpath(_bytespath(options.with_hg))
480 if not (os.path.isfile(options.with_hg) and
480 if not (os.path.isfile(options.with_hg) and
481 os.access(options.with_hg, os.X_OK)):
481 os.access(options.with_hg, os.X_OK)):
482 parser.error('--with-hg must specify an executable hg script')
482 parser.error('--with-hg must specify an executable hg script')
483 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
483 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
484 sys.stderr.write('warning: --with-hg should specify an hg script\n')
484 sys.stderr.write('warning: --with-hg should specify an hg script\n')
485 if options.local:
485 if options.local:
486 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
486 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
487 reporootdir = os.path.dirname(testdir)
487 reporootdir = os.path.dirname(testdir)
488 pathandattrs = [(b'hg', 'with_hg')]
488 pathandattrs = [(b'hg', 'with_hg')]
489 if options.chg:
489 if options.chg:
490 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
490 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
491 for relpath, attr in pathandattrs:
491 for relpath, attr in pathandattrs:
492 binpath = os.path.join(reporootdir, relpath)
492 binpath = os.path.join(reporootdir, relpath)
493 if os.name != 'nt' and not os.access(binpath, os.X_OK):
493 if os.name != 'nt' and not os.access(binpath, os.X_OK):
494 parser.error('--local specified, but %r not found or '
494 parser.error('--local specified, but %r not found or '
495 'not executable' % binpath)
495 'not executable' % binpath)
496 setattr(options, attr, binpath)
496 setattr(options, attr, binpath)
497
497
498 if (options.chg or options.with_chg) and os.name == 'nt':
498 if (options.chg or options.with_chg) and os.name == 'nt':
499 parser.error('chg does not work on %s' % os.name)
499 parser.error('chg does not work on %s' % os.name)
500 if options.with_chg:
500 if options.with_chg:
501 options.chg = False # no installation to temporary location
501 options.chg = False # no installation to temporary location
502 options.with_chg = canonpath(_bytespath(options.with_chg))
502 options.with_chg = canonpath(_bytespath(options.with_chg))
503 if not (os.path.isfile(options.with_chg) and
503 if not (os.path.isfile(options.with_chg) and
504 os.access(options.with_chg, os.X_OK)):
504 os.access(options.with_chg, os.X_OK)):
505 parser.error('--with-chg must specify a chg executable')
505 parser.error('--with-chg must specify a chg executable')
506 if options.chg and options.with_hg:
506 if options.chg and options.with_hg:
507 # chg shares installation location with hg
507 # chg shares installation location with hg
508 parser.error('--chg does not work when --with-hg is specified '
508 parser.error('--chg does not work when --with-hg is specified '
509 '(use --with-chg instead)')
509 '(use --with-chg instead)')
510
510
511 if options.color == 'always' and not pygmentspresent:
511 if options.color == 'always' and not pygmentspresent:
512 sys.stderr.write('warning: --color=always ignored because '
512 sys.stderr.write('warning: --color=always ignored because '
513 'pygments is not installed\n')
513 'pygments is not installed\n')
514
514
515 if options.bisect_repo and not options.known_good_rev:
515 if options.bisect_repo and not options.known_good_rev:
516 parser.error("--bisect-repo cannot be used without --known-good-rev")
516 parser.error("--bisect-repo cannot be used without --known-good-rev")
517
517
518 global useipv6
518 global useipv6
519 if options.ipv6:
519 if options.ipv6:
520 useipv6 = checksocketfamily('AF_INET6')
520 useipv6 = checksocketfamily('AF_INET6')
521 else:
521 else:
522 # only use IPv6 if IPv4 is unavailable and IPv6 is available
522 # only use IPv6 if IPv4 is unavailable and IPv6 is available
523 useipv6 = ((not checksocketfamily('AF_INET'))
523 useipv6 = ((not checksocketfamily('AF_INET'))
524 and checksocketfamily('AF_INET6'))
524 and checksocketfamily('AF_INET6'))
525
525
526 options.anycoverage = options.cover or options.annotate or options.htmlcov
526 options.anycoverage = options.cover or options.annotate or options.htmlcov
527 if options.anycoverage:
527 if options.anycoverage:
528 try:
528 try:
529 import coverage
529 import coverage
530 covver = version.StrictVersion(coverage.__version__).version
530 covver = version.StrictVersion(coverage.__version__).version
531 if covver < (3, 3):
531 if covver < (3, 3):
532 parser.error('coverage options require coverage 3.3 or later')
532 parser.error('coverage options require coverage 3.3 or later')
533 except ImportError:
533 except ImportError:
534 parser.error('coverage options now require the coverage package')
534 parser.error('coverage options now require the coverage package')
535
535
536 if options.anycoverage and options.local:
536 if options.anycoverage and options.local:
537 # this needs some path mangling somewhere, I guess
537 # this needs some path mangling somewhere, I guess
538 parser.error("sorry, coverage options do not work when --local "
538 parser.error("sorry, coverage options do not work when --local "
539 "is specified")
539 "is specified")
540
540
541 if options.anycoverage and options.with_hg:
541 if options.anycoverage and options.with_hg:
542 parser.error("sorry, coverage options do not work when --with-hg "
542 parser.error("sorry, coverage options do not work when --with-hg "
543 "is specified")
543 "is specified")
544
544
545 global verbose
545 global verbose
546 if options.verbose:
546 if options.verbose:
547 verbose = ''
547 verbose = ''
548
548
549 if options.tmpdir:
549 if options.tmpdir:
550 options.tmpdir = canonpath(options.tmpdir)
550 options.tmpdir = canonpath(options.tmpdir)
551
551
552 if options.jobs < 1:
552 if options.jobs < 1:
553 parser.error('--jobs must be positive')
553 parser.error('--jobs must be positive')
554 if options.interactive and options.debug:
554 if options.interactive and options.debug:
555 parser.error("-i/--interactive and -d/--debug are incompatible")
555 parser.error("-i/--interactive and -d/--debug are incompatible")
556 if options.debug:
556 if options.debug:
557 if options.timeout != defaults['timeout']:
557 if options.timeout != defaults['timeout']:
558 sys.stderr.write(
558 sys.stderr.write(
559 'warning: --timeout option ignored with --debug\n')
559 'warning: --timeout option ignored with --debug\n')
560 if options.slowtimeout != defaults['slowtimeout']:
560 if options.slowtimeout != defaults['slowtimeout']:
561 sys.stderr.write(
561 sys.stderr.write(
562 'warning: --slowtimeout option ignored with --debug\n')
562 'warning: --slowtimeout option ignored with --debug\n')
563 options.timeout = 0
563 options.timeout = 0
564 options.slowtimeout = 0
564 options.slowtimeout = 0
565 if options.py3k_warnings:
565 if options.py3_warnings:
566 if PYTHON3:
566 if PYTHON3:
567 parser.error(
567 parser.error(
568 '--py3k-warnings can only be used on Python 2.7')
568 '--py3-warnings can only be used on Python 2.7')
569
569
570 if options.blacklist:
570 if options.blacklist:
571 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
571 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
572 if options.whitelist:
572 if options.whitelist:
573 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
573 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
574 else:
574 else:
575 options.whitelisted = {}
575 options.whitelisted = {}
576
576
577 if options.showchannels:
577 if options.showchannels:
578 options.nodiff = True
578 options.nodiff = True
579
579
580 return options
580 return options
581
581
582 def rename(src, dst):
582 def rename(src, dst):
583 """Like os.rename(), trade atomicity and opened files friendliness
583 """Like os.rename(), trade atomicity and opened files friendliness
584 for existing destination support.
584 for existing destination support.
585 """
585 """
586 shutil.copy(src, dst)
586 shutil.copy(src, dst)
587 os.remove(src)
587 os.remove(src)
588
588
589 _unified_diff = difflib.unified_diff
589 _unified_diff = difflib.unified_diff
590 if PYTHON3:
590 if PYTHON3:
591 import functools
591 import functools
592 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
592 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
593
593
594 def getdiff(expected, output, ref, err):
594 def getdiff(expected, output, ref, err):
595 servefail = False
595 servefail = False
596 lines = []
596 lines = []
597 for line in _unified_diff(expected, output, ref, err):
597 for line in _unified_diff(expected, output, ref, err):
598 if line.startswith(b'+++') or line.startswith(b'---'):
598 if line.startswith(b'+++') or line.startswith(b'---'):
599 line = line.replace(b'\\', b'/')
599 line = line.replace(b'\\', b'/')
600 if line.endswith(b' \n'):
600 if line.endswith(b' \n'):
601 line = line[:-2] + b'\n'
601 line = line[:-2] + b'\n'
602 lines.append(line)
602 lines.append(line)
603 if not servefail and line.startswith(
603 if not servefail and line.startswith(
604 b'+ abort: child process failed to start'):
604 b'+ abort: child process failed to start'):
605 servefail = True
605 servefail = True
606
606
607 return servefail, lines
607 return servefail, lines
608
608
609 verbose = False
609 verbose = False
610 def vlog(*msg):
610 def vlog(*msg):
611 """Log only when in verbose mode."""
611 """Log only when in verbose mode."""
612 if verbose is False:
612 if verbose is False:
613 return
613 return
614
614
615 return log(*msg)
615 return log(*msg)
616
616
617 # Bytes that break XML even in a CDATA block: control characters 0-31
617 # Bytes that break XML even in a CDATA block: control characters 0-31
618 # sans \t, \n and \r
618 # sans \t, \n and \r
619 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
619 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
620
620
621 # Match feature conditionalized output lines in the form, capturing the feature
621 # Match feature conditionalized output lines in the form, capturing the feature
622 # list in group 2, and the preceeding line output in group 1:
622 # list in group 2, and the preceeding line output in group 1:
623 #
623 #
624 # output..output (feature !)\n
624 # output..output (feature !)\n
625 optline = re.compile(b'(.*) \((.+?) !\)\n$')
625 optline = re.compile(b'(.*) \((.+?) !\)\n$')
626
626
627 def cdatasafe(data):
627 def cdatasafe(data):
628 """Make a string safe to include in a CDATA block.
628 """Make a string safe to include in a CDATA block.
629
629
630 Certain control characters are illegal in a CDATA block, and
630 Certain control characters are illegal in a CDATA block, and
631 there's no way to include a ]]> in a CDATA either. This function
631 there's no way to include a ]]> in a CDATA either. This function
632 replaces illegal bytes with ? and adds a space between the ]] so
632 replaces illegal bytes with ? and adds a space between the ]] so
633 that it won't break the CDATA block.
633 that it won't break the CDATA block.
634 """
634 """
635 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
635 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
636
636
637 def log(*msg):
637 def log(*msg):
638 """Log something to stdout.
638 """Log something to stdout.
639
639
640 Arguments are strings to print.
640 Arguments are strings to print.
641 """
641 """
642 with iolock:
642 with iolock:
643 if verbose:
643 if verbose:
644 print(verbose, end=' ')
644 print(verbose, end=' ')
645 for m in msg:
645 for m in msg:
646 print(m, end=' ')
646 print(m, end=' ')
647 print()
647 print()
648 sys.stdout.flush()
648 sys.stdout.flush()
649
649
650 def highlightdiff(line, color):
650 def highlightdiff(line, color):
651 if not color:
651 if not color:
652 return line
652 return line
653 assert pygmentspresent
653 assert pygmentspresent
654 return pygments.highlight(line.decode('latin1'), difflexer,
654 return pygments.highlight(line.decode('latin1'), difflexer,
655 terminal256formatter).encode('latin1')
655 terminal256formatter).encode('latin1')
656
656
657 def highlightmsg(msg, color):
657 def highlightmsg(msg, color):
658 if not color:
658 if not color:
659 return msg
659 return msg
660 assert pygmentspresent
660 assert pygmentspresent
661 return pygments.highlight(msg, runnerlexer, runnerformatter)
661 return pygments.highlight(msg, runnerlexer, runnerformatter)
662
662
663 def terminate(proc):
663 def terminate(proc):
664 """Terminate subprocess"""
664 """Terminate subprocess"""
665 vlog('# Terminating process %d' % proc.pid)
665 vlog('# Terminating process %d' % proc.pid)
666 try:
666 try:
667 proc.terminate()
667 proc.terminate()
668 except OSError:
668 except OSError:
669 pass
669 pass
670
670
671 def killdaemons(pidfile):
671 def killdaemons(pidfile):
672 import killdaemons as killmod
672 import killdaemons as killmod
673 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
673 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
674 logfn=vlog)
674 logfn=vlog)
675
675
676 class Test(unittest.TestCase):
676 class Test(unittest.TestCase):
677 """Encapsulates a single, runnable test.
677 """Encapsulates a single, runnable test.
678
678
679 While this class conforms to the unittest.TestCase API, it differs in that
679 While this class conforms to the unittest.TestCase API, it differs in that
680 instances need to be instantiated manually. (Typically, unittest.TestCase
680 instances need to be instantiated manually. (Typically, unittest.TestCase
681 classes are instantiated automatically by scanning modules.)
681 classes are instantiated automatically by scanning modules.)
682 """
682 """
683
683
684 # Status code reserved for skipped tests (used by hghave).
684 # Status code reserved for skipped tests (used by hghave).
685 SKIPPED_STATUS = 80
685 SKIPPED_STATUS = 80
686
686
687 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
687 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
688 debug=False,
688 debug=False,
689 first=False,
689 first=False,
690 timeout=None,
690 timeout=None,
691 startport=None, extraconfigopts=None,
691 startport=None, extraconfigopts=None,
692 py3kwarnings=False, shell=None, hgcommand=None,
692 py3warnings=False, shell=None, hgcommand=None,
693 slowtimeout=None, usechg=False,
693 slowtimeout=None, usechg=False,
694 useipv6=False):
694 useipv6=False):
695 """Create a test from parameters.
695 """Create a test from parameters.
696
696
697 path is the full path to the file defining the test.
697 path is the full path to the file defining the test.
698
698
699 tmpdir is the main temporary directory to use for this test.
699 tmpdir is the main temporary directory to use for this test.
700
700
701 keeptmpdir determines whether to keep the test's temporary directory
701 keeptmpdir determines whether to keep the test's temporary directory
702 after execution. It defaults to removal (False).
702 after execution. It defaults to removal (False).
703
703
704 debug mode will make the test execute verbosely, with unfiltered
704 debug mode will make the test execute verbosely, with unfiltered
705 output.
705 output.
706
706
707 timeout controls the maximum run time of the test. It is ignored when
707 timeout controls the maximum run time of the test. It is ignored when
708 debug is True. See slowtimeout for tests with #require slow.
708 debug is True. See slowtimeout for tests with #require slow.
709
709
710 slowtimeout overrides timeout if the test has #require slow.
710 slowtimeout overrides timeout if the test has #require slow.
711
711
712 startport controls the starting port number to use for this test. Each
712 startport controls the starting port number to use for this test. Each
713 test will reserve 3 port numbers for execution. It is the caller's
713 test will reserve 3 port numbers for execution. It is the caller's
714 responsibility to allocate a non-overlapping port range to Test
714 responsibility to allocate a non-overlapping port range to Test
715 instances.
715 instances.
716
716
717 extraconfigopts is an iterable of extra hgrc config options. Values
717 extraconfigopts is an iterable of extra hgrc config options. Values
718 must have the form "key=value" (something understood by hgrc). Values
718 must have the form "key=value" (something understood by hgrc). Values
719 of the form "foo.key=value" will result in "[foo] key=value".
719 of the form "foo.key=value" will result in "[foo] key=value".
720
720
721 py3kwarnings enables Py3k warnings.
721 py3warnings enables Py3k warnings.
722
722
723 shell is the shell to execute tests in.
723 shell is the shell to execute tests in.
724 """
724 """
725 if timeout is None:
725 if timeout is None:
726 timeout = defaults['timeout']
726 timeout = defaults['timeout']
727 if startport is None:
727 if startport is None:
728 startport = defaults['port']
728 startport = defaults['port']
729 if slowtimeout is None:
729 if slowtimeout is None:
730 slowtimeout = defaults['slowtimeout']
730 slowtimeout = defaults['slowtimeout']
731 self.path = path
731 self.path = path
732 self.bname = os.path.basename(path)
732 self.bname = os.path.basename(path)
733 self.name = _strpath(self.bname)
733 self.name = _strpath(self.bname)
734 self._testdir = os.path.dirname(path)
734 self._testdir = os.path.dirname(path)
735 self._outputdir = outputdir
735 self._outputdir = outputdir
736 self._tmpname = os.path.basename(path)
736 self._tmpname = os.path.basename(path)
737 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
737 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
738
738
739 self._threadtmp = tmpdir
739 self._threadtmp = tmpdir
740 self._keeptmpdir = keeptmpdir
740 self._keeptmpdir = keeptmpdir
741 self._debug = debug
741 self._debug = debug
742 self._first = first
742 self._first = first
743 self._timeout = timeout
743 self._timeout = timeout
744 self._slowtimeout = slowtimeout
744 self._slowtimeout = slowtimeout
745 self._startport = startport
745 self._startport = startport
746 self._extraconfigopts = extraconfigopts or []
746 self._extraconfigopts = extraconfigopts or []
747 self._py3kwarnings = py3kwarnings
747 self._py3warnings = py3warnings
748 self._shell = _bytespath(shell)
748 self._shell = _bytespath(shell)
749 self._hgcommand = hgcommand or b'hg'
749 self._hgcommand = hgcommand or b'hg'
750 self._usechg = usechg
750 self._usechg = usechg
751 self._useipv6 = useipv6
751 self._useipv6 = useipv6
752
752
753 self._aborted = False
753 self._aborted = False
754 self._daemonpids = []
754 self._daemonpids = []
755 self._finished = None
755 self._finished = None
756 self._ret = None
756 self._ret = None
757 self._out = None
757 self._out = None
758 self._skipped = None
758 self._skipped = None
759 self._testtmp = None
759 self._testtmp = None
760 self._chgsockdir = None
760 self._chgsockdir = None
761
761
762 self._refout = self.readrefout()
762 self._refout = self.readrefout()
763
763
764 def readrefout(self):
764 def readrefout(self):
765 """read reference output"""
765 """read reference output"""
766 # If we're not in --debug mode and reference output file exists,
766 # If we're not in --debug mode and reference output file exists,
767 # check test output against it.
767 # check test output against it.
768 if self._debug:
768 if self._debug:
769 return None # to match "out is None"
769 return None # to match "out is None"
770 elif os.path.exists(self.refpath):
770 elif os.path.exists(self.refpath):
771 with open(self.refpath, 'rb') as f:
771 with open(self.refpath, 'rb') as f:
772 return f.read().splitlines(True)
772 return f.read().splitlines(True)
773 else:
773 else:
774 return []
774 return []
775
775
776 # needed to get base class __repr__ running
776 # needed to get base class __repr__ running
777 @property
777 @property
778 def _testMethodName(self):
778 def _testMethodName(self):
779 return self.name
779 return self.name
780
780
781 def __str__(self):
781 def __str__(self):
782 return self.name
782 return self.name
783
783
784 def shortDescription(self):
784 def shortDescription(self):
785 return self.name
785 return self.name
786
786
787 def setUp(self):
787 def setUp(self):
788 """Tasks to perform before run()."""
788 """Tasks to perform before run()."""
789 self._finished = False
789 self._finished = False
790 self._ret = None
790 self._ret = None
791 self._out = None
791 self._out = None
792 self._skipped = None
792 self._skipped = None
793
793
794 try:
794 try:
795 os.mkdir(self._threadtmp)
795 os.mkdir(self._threadtmp)
796 except OSError as e:
796 except OSError as e:
797 if e.errno != errno.EEXIST:
797 if e.errno != errno.EEXIST:
798 raise
798 raise
799
799
800 name = self._tmpname
800 name = self._tmpname
801 self._testtmp = os.path.join(self._threadtmp, name)
801 self._testtmp = os.path.join(self._threadtmp, name)
802 os.mkdir(self._testtmp)
802 os.mkdir(self._testtmp)
803
803
804 # Remove any previous output files.
804 # Remove any previous output files.
805 if os.path.exists(self.errpath):
805 if os.path.exists(self.errpath):
806 try:
806 try:
807 os.remove(self.errpath)
807 os.remove(self.errpath)
808 except OSError as e:
808 except OSError as e:
809 # We might have raced another test to clean up a .err
809 # We might have raced another test to clean up a .err
810 # file, so ignore ENOENT when removing a previous .err
810 # file, so ignore ENOENT when removing a previous .err
811 # file.
811 # file.
812 if e.errno != errno.ENOENT:
812 if e.errno != errno.ENOENT:
813 raise
813 raise
814
814
815 if self._usechg:
815 if self._usechg:
816 self._chgsockdir = os.path.join(self._threadtmp,
816 self._chgsockdir = os.path.join(self._threadtmp,
817 b'%s.chgsock' % name)
817 b'%s.chgsock' % name)
818 os.mkdir(self._chgsockdir)
818 os.mkdir(self._chgsockdir)
819
819
820 def run(self, result):
820 def run(self, result):
821 """Run this test and report results against a TestResult instance."""
821 """Run this test and report results against a TestResult instance."""
822 # This function is extremely similar to unittest.TestCase.run(). Once
822 # This function is extremely similar to unittest.TestCase.run(). Once
823 # we require Python 2.7 (or at least its version of unittest), this
823 # we require Python 2.7 (or at least its version of unittest), this
824 # function can largely go away.
824 # function can largely go away.
825 self._result = result
825 self._result = result
826 result.startTest(self)
826 result.startTest(self)
827 try:
827 try:
828 try:
828 try:
829 self.setUp()
829 self.setUp()
830 except (KeyboardInterrupt, SystemExit):
830 except (KeyboardInterrupt, SystemExit):
831 self._aborted = True
831 self._aborted = True
832 raise
832 raise
833 except Exception:
833 except Exception:
834 result.addError(self, sys.exc_info())
834 result.addError(self, sys.exc_info())
835 return
835 return
836
836
837 success = False
837 success = False
838 try:
838 try:
839 self.runTest()
839 self.runTest()
840 except KeyboardInterrupt:
840 except KeyboardInterrupt:
841 self._aborted = True
841 self._aborted = True
842 raise
842 raise
843 except unittest.SkipTest as e:
843 except unittest.SkipTest as e:
844 result.addSkip(self, str(e))
844 result.addSkip(self, str(e))
845 # The base class will have already counted this as a
845 # The base class will have already counted this as a
846 # test we "ran", but we want to exclude skipped tests
846 # test we "ran", but we want to exclude skipped tests
847 # from those we count towards those run.
847 # from those we count towards those run.
848 result.testsRun -= 1
848 result.testsRun -= 1
849 except self.failureException as e:
849 except self.failureException as e:
850 # This differs from unittest in that we don't capture
850 # This differs from unittest in that we don't capture
851 # the stack trace. This is for historical reasons and
851 # the stack trace. This is for historical reasons and
852 # this decision could be revisited in the future,
852 # this decision could be revisited in the future,
853 # especially for PythonTest instances.
853 # especially for PythonTest instances.
854 if result.addFailure(self, str(e)):
854 if result.addFailure(self, str(e)):
855 success = True
855 success = True
856 except Exception:
856 except Exception:
857 result.addError(self, sys.exc_info())
857 result.addError(self, sys.exc_info())
858 else:
858 else:
859 success = True
859 success = True
860
860
861 try:
861 try:
862 self.tearDown()
862 self.tearDown()
863 except (KeyboardInterrupt, SystemExit):
863 except (KeyboardInterrupt, SystemExit):
864 self._aborted = True
864 self._aborted = True
865 raise
865 raise
866 except Exception:
866 except Exception:
867 result.addError(self, sys.exc_info())
867 result.addError(self, sys.exc_info())
868 success = False
868 success = False
869
869
870 if success:
870 if success:
871 result.addSuccess(self)
871 result.addSuccess(self)
872 finally:
872 finally:
873 result.stopTest(self, interrupted=self._aborted)
873 result.stopTest(self, interrupted=self._aborted)
874
874
875 def runTest(self):
875 def runTest(self):
876 """Run this test instance.
876 """Run this test instance.
877
877
878 This will return a tuple describing the result of the test.
878 This will return a tuple describing the result of the test.
879 """
879 """
880 env = self._getenv()
880 env = self._getenv()
881 self._genrestoreenv(env)
881 self._genrestoreenv(env)
882 self._daemonpids.append(env['DAEMON_PIDS'])
882 self._daemonpids.append(env['DAEMON_PIDS'])
883 self._createhgrc(env['HGRCPATH'])
883 self._createhgrc(env['HGRCPATH'])
884
884
885 vlog('# Test', self.name)
885 vlog('# Test', self.name)
886
886
887 ret, out = self._run(env)
887 ret, out = self._run(env)
888 self._finished = True
888 self._finished = True
889 self._ret = ret
889 self._ret = ret
890 self._out = out
890 self._out = out
891
891
892 def describe(ret):
892 def describe(ret):
893 if ret < 0:
893 if ret < 0:
894 return 'killed by signal: %d' % -ret
894 return 'killed by signal: %d' % -ret
895 return 'returned error code %d' % ret
895 return 'returned error code %d' % ret
896
896
897 self._skipped = False
897 self._skipped = False
898
898
899 if ret == self.SKIPPED_STATUS:
899 if ret == self.SKIPPED_STATUS:
900 if out is None: # Debug mode, nothing to parse.
900 if out is None: # Debug mode, nothing to parse.
901 missing = ['unknown']
901 missing = ['unknown']
902 failed = None
902 failed = None
903 else:
903 else:
904 missing, failed = TTest.parsehghaveoutput(out)
904 missing, failed = TTest.parsehghaveoutput(out)
905
905
906 if not missing:
906 if not missing:
907 missing = ['skipped']
907 missing = ['skipped']
908
908
909 if failed:
909 if failed:
910 self.fail('hg have failed checking for %s' % failed[-1])
910 self.fail('hg have failed checking for %s' % failed[-1])
911 else:
911 else:
912 self._skipped = True
912 self._skipped = True
913 raise unittest.SkipTest(missing[-1])
913 raise unittest.SkipTest(missing[-1])
914 elif ret == 'timeout':
914 elif ret == 'timeout':
915 self.fail('timed out')
915 self.fail('timed out')
916 elif ret is False:
916 elif ret is False:
917 self.fail('no result code from test')
917 self.fail('no result code from test')
918 elif out != self._refout:
918 elif out != self._refout:
919 # Diff generation may rely on written .err file.
919 # Diff generation may rely on written .err file.
920 if (ret != 0 or out != self._refout) and not self._skipped \
920 if (ret != 0 or out != self._refout) and not self._skipped \
921 and not self._debug:
921 and not self._debug:
922 with open(self.errpath, 'wb') as f:
922 with open(self.errpath, 'wb') as f:
923 for line in out:
923 for line in out:
924 f.write(line)
924 f.write(line)
925
925
926 # The result object handles diff calculation for us.
926 # The result object handles diff calculation for us.
927 with firstlock:
927 with firstlock:
928 if self._result.addOutputMismatch(self, ret, out, self._refout):
928 if self._result.addOutputMismatch(self, ret, out, self._refout):
929 # change was accepted, skip failing
929 # change was accepted, skip failing
930 return
930 return
931 if self._first:
931 if self._first:
932 global firsterror
932 global firsterror
933 firsterror = True
933 firsterror = True
934
934
935 if ret:
935 if ret:
936 msg = 'output changed and ' + describe(ret)
936 msg = 'output changed and ' + describe(ret)
937 else:
937 else:
938 msg = 'output changed'
938 msg = 'output changed'
939
939
940 self.fail(msg)
940 self.fail(msg)
941 elif ret:
941 elif ret:
942 self.fail(describe(ret))
942 self.fail(describe(ret))
943
943
944 def tearDown(self):
944 def tearDown(self):
945 """Tasks to perform after run()."""
945 """Tasks to perform after run()."""
946 for entry in self._daemonpids:
946 for entry in self._daemonpids:
947 killdaemons(entry)
947 killdaemons(entry)
948 self._daemonpids = []
948 self._daemonpids = []
949
949
950 if self._keeptmpdir:
950 if self._keeptmpdir:
951 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
951 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
952 (self._testtmp.decode('utf-8'),
952 (self._testtmp.decode('utf-8'),
953 self._threadtmp.decode('utf-8')))
953 self._threadtmp.decode('utf-8')))
954 else:
954 else:
955 shutil.rmtree(self._testtmp, True)
955 shutil.rmtree(self._testtmp, True)
956 shutil.rmtree(self._threadtmp, True)
956 shutil.rmtree(self._threadtmp, True)
957
957
958 if self._usechg:
958 if self._usechg:
959 # chgservers will stop automatically after they find the socket
959 # chgservers will stop automatically after they find the socket
960 # files are deleted
960 # files are deleted
961 shutil.rmtree(self._chgsockdir, True)
961 shutil.rmtree(self._chgsockdir, True)
962
962
963 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
963 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
964 and not self._debug and self._out:
964 and not self._debug and self._out:
965 with open(self.errpath, 'wb') as f:
965 with open(self.errpath, 'wb') as f:
966 for line in self._out:
966 for line in self._out:
967 f.write(line)
967 f.write(line)
968
968
969 vlog("# Ret was:", self._ret, '(%s)' % self.name)
969 vlog("# Ret was:", self._ret, '(%s)' % self.name)
970
970
971 def _run(self, env):
971 def _run(self, env):
972 # This should be implemented in child classes to run tests.
972 # This should be implemented in child classes to run tests.
973 raise unittest.SkipTest('unknown test type')
973 raise unittest.SkipTest('unknown test type')
974
974
975 def abort(self):
975 def abort(self):
976 """Terminate execution of this test."""
976 """Terminate execution of this test."""
977 self._aborted = True
977 self._aborted = True
978
978
979 def _portmap(self, i):
979 def _portmap(self, i):
980 offset = b'' if i == 0 else b'%d' % i
980 offset = b'' if i == 0 else b'%d' % i
981 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
981 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
982
982
983 def _getreplacements(self):
983 def _getreplacements(self):
984 """Obtain a mapping of text replacements to apply to test output.
984 """Obtain a mapping of text replacements to apply to test output.
985
985
986 Test output needs to be normalized so it can be compared to expected
986 Test output needs to be normalized so it can be compared to expected
987 output. This function defines how some of that normalization will
987 output. This function defines how some of that normalization will
988 occur.
988 occur.
989 """
989 """
990 r = [
990 r = [
991 # This list should be parallel to defineport in _getenv
991 # This list should be parallel to defineport in _getenv
992 self._portmap(0),
992 self._portmap(0),
993 self._portmap(1),
993 self._portmap(1),
994 self._portmap(2),
994 self._portmap(2),
995 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
995 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
996 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
996 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
997 ]
997 ]
998 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
998 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
999
999
1000 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1000 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1001
1001
1002 if os.path.exists(replacementfile):
1002 if os.path.exists(replacementfile):
1003 data = {}
1003 data = {}
1004 with open(replacementfile, mode='rb') as source:
1004 with open(replacementfile, mode='rb') as source:
1005 # the intermediate 'compile' step help with debugging
1005 # the intermediate 'compile' step help with debugging
1006 code = compile(source.read(), replacementfile, 'exec')
1006 code = compile(source.read(), replacementfile, 'exec')
1007 exec(code, data)
1007 exec(code, data)
1008 for value in data.get('substitutions', ()):
1008 for value in data.get('substitutions', ()):
1009 if len(value) != 2:
1009 if len(value) != 2:
1010 msg = 'malformatted substitution in %s: %r'
1010 msg = 'malformatted substitution in %s: %r'
1011 msg %= (replacementfile, value)
1011 msg %= (replacementfile, value)
1012 raise ValueError(msg)
1012 raise ValueError(msg)
1013 r.append(value)
1013 r.append(value)
1014 return r
1014 return r
1015
1015
1016 def _escapepath(self, p):
1016 def _escapepath(self, p):
1017 if os.name == 'nt':
1017 if os.name == 'nt':
1018 return (
1018 return (
1019 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
1019 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
1020 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
1020 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
1021 for c in [p[i:i + 1] for i in range(len(p))]))
1021 for c in [p[i:i + 1] for i in range(len(p))]))
1022 )
1022 )
1023 else:
1023 else:
1024 return re.escape(p)
1024 return re.escape(p)
1025
1025
1026 def _localip(self):
1026 def _localip(self):
1027 if self._useipv6:
1027 if self._useipv6:
1028 return b'::1'
1028 return b'::1'
1029 else:
1029 else:
1030 return b'127.0.0.1'
1030 return b'127.0.0.1'
1031
1031
1032 def _genrestoreenv(self, testenv):
1032 def _genrestoreenv(self, testenv):
1033 """Generate a script that can be used by tests to restore the original
1033 """Generate a script that can be used by tests to restore the original
1034 environment."""
1034 environment."""
1035 # Put the restoreenv script inside self._threadtmp
1035 # Put the restoreenv script inside self._threadtmp
1036 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1036 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1037 testenv['HGTEST_RESTOREENV'] = _strpath(scriptpath)
1037 testenv['HGTEST_RESTOREENV'] = _strpath(scriptpath)
1038
1038
1039 # Only restore environment variable names that the shell allows
1039 # Only restore environment variable names that the shell allows
1040 # us to export.
1040 # us to export.
1041 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1041 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1042
1042
1043 # Do not restore these variables; otherwise tests would fail.
1043 # Do not restore these variables; otherwise tests would fail.
1044 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1044 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1045
1045
1046 with open(scriptpath, 'w') as envf:
1046 with open(scriptpath, 'w') as envf:
1047 for name, value in origenviron.items():
1047 for name, value in origenviron.items():
1048 if not name_regex.match(name):
1048 if not name_regex.match(name):
1049 # Skip environment variables with unusual names not
1049 # Skip environment variables with unusual names not
1050 # allowed by most shells.
1050 # allowed by most shells.
1051 continue
1051 continue
1052 if name in reqnames:
1052 if name in reqnames:
1053 continue
1053 continue
1054 envf.write('%s=%s\n' % (name, shellquote(value)))
1054 envf.write('%s=%s\n' % (name, shellquote(value)))
1055
1055
1056 for name in testenv:
1056 for name in testenv:
1057 if name in origenviron or name in reqnames:
1057 if name in origenviron or name in reqnames:
1058 continue
1058 continue
1059 envf.write('unset %s\n' % (name,))
1059 envf.write('unset %s\n' % (name,))
1060
1060
1061 def _getenv(self):
1061 def _getenv(self):
1062 """Obtain environment variables to use during test execution."""
1062 """Obtain environment variables to use during test execution."""
1063 def defineport(i):
1063 def defineport(i):
1064 offset = '' if i == 0 else '%s' % i
1064 offset = '' if i == 0 else '%s' % i
1065 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1065 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1066 env = os.environ.copy()
1066 env = os.environ.copy()
1067 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1067 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1068 env['HGEMITWARNINGS'] = '1'
1068 env['HGEMITWARNINGS'] = '1'
1069 env['TESTTMP'] = _strpath(self._testtmp)
1069 env['TESTTMP'] = _strpath(self._testtmp)
1070 env['TESTNAME'] = self.name
1070 env['TESTNAME'] = self.name
1071 env['HOME'] = _strpath(self._testtmp)
1071 env['HOME'] = _strpath(self._testtmp)
1072 # This number should match portneeded in _getport
1072 # This number should match portneeded in _getport
1073 for port in xrange(3):
1073 for port in xrange(3):
1074 # This list should be parallel to _portmap in _getreplacements
1074 # This list should be parallel to _portmap in _getreplacements
1075 defineport(port)
1075 defineport(port)
1076 env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
1076 env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
1077 env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp,
1077 env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp,
1078 b'daemon.pids'))
1078 b'daemon.pids'))
1079 env["HGEDITOR"] = ('"' + sys.executable + '"'
1079 env["HGEDITOR"] = ('"' + sys.executable + '"'
1080 + ' -c "import sys; sys.exit(0)"')
1080 + ' -c "import sys; sys.exit(0)"')
1081 env["HGMERGE"] = "internal:merge"
1081 env["HGMERGE"] = "internal:merge"
1082 env["HGUSER"] = "test"
1082 env["HGUSER"] = "test"
1083 env["HGENCODING"] = "ascii"
1083 env["HGENCODING"] = "ascii"
1084 env["HGENCODINGMODE"] = "strict"
1084 env["HGENCODINGMODE"] = "strict"
1085 env["HGHOSTNAME"] = "test-hostname"
1085 env["HGHOSTNAME"] = "test-hostname"
1086 env['HGIPV6'] = str(int(self._useipv6))
1086 env['HGIPV6'] = str(int(self._useipv6))
1087 if 'HGCATAPULTSERVERPIPE' not in env:
1087 if 'HGCATAPULTSERVERPIPE' not in env:
1088 env['HGCATAPULTSERVERPIPE'] = os.devnull
1088 env['HGCATAPULTSERVERPIPE'] = os.devnull
1089
1089
1090 extraextensions = []
1090 extraextensions = []
1091 for opt in self._extraconfigopts:
1091 for opt in self._extraconfigopts:
1092 section, key = opt.encode('utf-8').split(b'.', 1)
1092 section, key = opt.encode('utf-8').split(b'.', 1)
1093 if section != 'extensions':
1093 if section != 'extensions':
1094 continue
1094 continue
1095 name = key.split(b'=', 1)[0]
1095 name = key.split(b'=', 1)[0]
1096 extraextensions.append(name)
1096 extraextensions.append(name)
1097
1097
1098 if extraextensions:
1098 if extraextensions:
1099 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1099 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1100
1100
1101 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1101 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1102 # IP addresses.
1102 # IP addresses.
1103 env['LOCALIP'] = _strpath(self._localip())
1103 env['LOCALIP'] = _strpath(self._localip())
1104
1104
1105 # Reset some environment variables to well-known values so that
1105 # Reset some environment variables to well-known values so that
1106 # the tests produce repeatable output.
1106 # the tests produce repeatable output.
1107 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1107 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1108 env['TZ'] = 'GMT'
1108 env['TZ'] = 'GMT'
1109 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1109 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1110 env['COLUMNS'] = '80'
1110 env['COLUMNS'] = '80'
1111 env['TERM'] = 'xterm'
1111 env['TERM'] = 'xterm'
1112
1112
1113 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
1113 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
1114 'HGPLAIN HGPLAINEXCEPT EDITOR VISUAL PAGER ' +
1114 'HGPLAIN HGPLAINEXCEPT EDITOR VISUAL PAGER ' +
1115 'NO_PROXY CHGDEBUG').split():
1115 'NO_PROXY CHGDEBUG').split():
1116 if k in env:
1116 if k in env:
1117 del env[k]
1117 del env[k]
1118
1118
1119 # unset env related to hooks
1119 # unset env related to hooks
1120 for k in list(env):
1120 for k in list(env):
1121 if k.startswith('HG_'):
1121 if k.startswith('HG_'):
1122 del env[k]
1122 del env[k]
1123
1123
1124 if self._usechg:
1124 if self._usechg:
1125 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1125 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1126
1126
1127 return env
1127 return env
1128
1128
1129 def _createhgrc(self, path):
1129 def _createhgrc(self, path):
1130 """Create an hgrc file for this test."""
1130 """Create an hgrc file for this test."""
1131 with open(path, 'wb') as hgrc:
1131 with open(path, 'wb') as hgrc:
1132 hgrc.write(b'[ui]\n')
1132 hgrc.write(b'[ui]\n')
1133 hgrc.write(b'slash = True\n')
1133 hgrc.write(b'slash = True\n')
1134 hgrc.write(b'interactive = False\n')
1134 hgrc.write(b'interactive = False\n')
1135 hgrc.write(b'mergemarkers = detailed\n')
1135 hgrc.write(b'mergemarkers = detailed\n')
1136 hgrc.write(b'promptecho = True\n')
1136 hgrc.write(b'promptecho = True\n')
1137 hgrc.write(b'[defaults]\n')
1137 hgrc.write(b'[defaults]\n')
1138 hgrc.write(b'[devel]\n')
1138 hgrc.write(b'[devel]\n')
1139 hgrc.write(b'all-warnings = true\n')
1139 hgrc.write(b'all-warnings = true\n')
1140 hgrc.write(b'default-date = 0 0\n')
1140 hgrc.write(b'default-date = 0 0\n')
1141 hgrc.write(b'[largefiles]\n')
1141 hgrc.write(b'[largefiles]\n')
1142 hgrc.write(b'usercache = %s\n' %
1142 hgrc.write(b'usercache = %s\n' %
1143 (os.path.join(self._testtmp, b'.cache/largefiles')))
1143 (os.path.join(self._testtmp, b'.cache/largefiles')))
1144 hgrc.write(b'[lfs]\n')
1144 hgrc.write(b'[lfs]\n')
1145 hgrc.write(b'usercache = %s\n' %
1145 hgrc.write(b'usercache = %s\n' %
1146 (os.path.join(self._testtmp, b'.cache/lfs')))
1146 (os.path.join(self._testtmp, b'.cache/lfs')))
1147 hgrc.write(b'[web]\n')
1147 hgrc.write(b'[web]\n')
1148 hgrc.write(b'address = localhost\n')
1148 hgrc.write(b'address = localhost\n')
1149 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1149 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1150 hgrc.write(b'server-header = testing stub value\n')
1150 hgrc.write(b'server-header = testing stub value\n')
1151
1151
1152 for opt in self._extraconfigopts:
1152 for opt in self._extraconfigopts:
1153 section, key = opt.encode('utf-8').split(b'.', 1)
1153 section, key = opt.encode('utf-8').split(b'.', 1)
1154 assert b'=' in key, ('extra config opt %s must '
1154 assert b'=' in key, ('extra config opt %s must '
1155 'have an = for assignment' % opt)
1155 'have an = for assignment' % opt)
1156 hgrc.write(b'[%s]\n%s\n' % (section, key))
1156 hgrc.write(b'[%s]\n%s\n' % (section, key))
1157
1157
1158 def fail(self, msg):
1158 def fail(self, msg):
1159 # unittest differentiates between errored and failed.
1159 # unittest differentiates between errored and failed.
1160 # Failed is denoted by AssertionError (by default at least).
1160 # Failed is denoted by AssertionError (by default at least).
1161 raise AssertionError(msg)
1161 raise AssertionError(msg)
1162
1162
1163 def _runcommand(self, cmd, env, normalizenewlines=False):
1163 def _runcommand(self, cmd, env, normalizenewlines=False):
1164 """Run command in a sub-process, capturing the output (stdout and
1164 """Run command in a sub-process, capturing the output (stdout and
1165 stderr).
1165 stderr).
1166
1166
1167 Return a tuple (exitcode, output). output is None in debug mode.
1167 Return a tuple (exitcode, output). output is None in debug mode.
1168 """
1168 """
1169 if self._debug:
1169 if self._debug:
1170 proc = subprocess.Popen(_strpath(cmd), shell=True,
1170 proc = subprocess.Popen(_strpath(cmd), shell=True,
1171 cwd=_strpath(self._testtmp),
1171 cwd=_strpath(self._testtmp),
1172 env=env)
1172 env=env)
1173 ret = proc.wait()
1173 ret = proc.wait()
1174 return (ret, None)
1174 return (ret, None)
1175
1175
1176 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1176 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1177 def cleanup():
1177 def cleanup():
1178 terminate(proc)
1178 terminate(proc)
1179 ret = proc.wait()
1179 ret = proc.wait()
1180 if ret == 0:
1180 if ret == 0:
1181 ret = signal.SIGTERM << 8
1181 ret = signal.SIGTERM << 8
1182 killdaemons(env['DAEMON_PIDS'])
1182 killdaemons(env['DAEMON_PIDS'])
1183 return ret
1183 return ret
1184
1184
1185 output = b''
1185 output = b''
1186 proc.tochild.close()
1186 proc.tochild.close()
1187
1187
1188 try:
1188 try:
1189 output = proc.fromchild.read()
1189 output = proc.fromchild.read()
1190 except KeyboardInterrupt:
1190 except KeyboardInterrupt:
1191 vlog('# Handling keyboard interrupt')
1191 vlog('# Handling keyboard interrupt')
1192 cleanup()
1192 cleanup()
1193 raise
1193 raise
1194
1194
1195 ret = proc.wait()
1195 ret = proc.wait()
1196 if wifexited(ret):
1196 if wifexited(ret):
1197 ret = os.WEXITSTATUS(ret)
1197 ret = os.WEXITSTATUS(ret)
1198
1198
1199 if proc.timeout:
1199 if proc.timeout:
1200 ret = 'timeout'
1200 ret = 'timeout'
1201
1201
1202 if ret:
1202 if ret:
1203 killdaemons(env['DAEMON_PIDS'])
1203 killdaemons(env['DAEMON_PIDS'])
1204
1204
1205 for s, r in self._getreplacements():
1205 for s, r in self._getreplacements():
1206 output = re.sub(s, r, output)
1206 output = re.sub(s, r, output)
1207
1207
1208 if normalizenewlines:
1208 if normalizenewlines:
1209 output = output.replace(b'\r\n', b'\n')
1209 output = output.replace(b'\r\n', b'\n')
1210
1210
1211 return ret, output.splitlines(True)
1211 return ret, output.splitlines(True)
1212
1212
1213 class PythonTest(Test):
1213 class PythonTest(Test):
1214 """A Python-based test."""
1214 """A Python-based test."""
1215
1215
1216 @property
1216 @property
1217 def refpath(self):
1217 def refpath(self):
1218 return os.path.join(self._testdir, b'%s.out' % self.bname)
1218 return os.path.join(self._testdir, b'%s.out' % self.bname)
1219
1219
1220 def _run(self, env):
1220 def _run(self, env):
1221 py3kswitch = self._py3kwarnings and b' -3' or b''
1221 py3switch = self._py3warnings and b' -3' or b''
1222 cmd = b'"%s"%s "%s"' % (PYTHON, py3kswitch, self.path)
1222 cmd = b'%s%s "%s"' % (PYTHON, py3switch, self.path)
1223 vlog("# Running", cmd)
1223 vlog("# Running", cmd)
1224 normalizenewlines = os.name == 'nt'
1224 normalizenewlines = os.name == 'nt'
1225 result = self._runcommand(cmd, env,
1225 result = self._runcommand(cmd, env,
1226 normalizenewlines=normalizenewlines)
1226 normalizenewlines=normalizenewlines)
1227 if self._aborted:
1227 if self._aborted:
1228 raise KeyboardInterrupt()
1228 raise KeyboardInterrupt()
1229
1229
1230 return result
1230 return result
1231
1231
1232 # Some glob patterns apply only in some circumstances, so the script
1232 # Some glob patterns apply only in some circumstances, so the script
1233 # might want to remove (glob) annotations that otherwise should be
1233 # might want to remove (glob) annotations that otherwise should be
1234 # retained.
1234 # retained.
1235 checkcodeglobpats = [
1235 checkcodeglobpats = [
1236 # On Windows it looks like \ doesn't require a (glob), but we know
1236 # On Windows it looks like \ doesn't require a (glob), but we know
1237 # better.
1237 # better.
1238 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1238 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1239 re.compile(br'^moving \S+/.*[^)]$'),
1239 re.compile(br'^moving \S+/.*[^)]$'),
1240 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1240 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1241 # Not all platforms have 127.0.0.1 as loopback (though most do),
1241 # Not all platforms have 127.0.0.1 as loopback (though most do),
1242 # so we always glob that too.
1242 # so we always glob that too.
1243 re.compile(br'.*\$LOCALIP.*$'),
1243 re.compile(br'.*\$LOCALIP.*$'),
1244 ]
1244 ]
1245
1245
1246 bchr = chr
1246 bchr = chr
1247 if PYTHON3:
1247 if PYTHON3:
1248 bchr = lambda x: bytes([x])
1248 bchr = lambda x: bytes([x])
1249
1249
1250 class TTest(Test):
1250 class TTest(Test):
1251 """A "t test" is a test backed by a .t file."""
1251 """A "t test" is a test backed by a .t file."""
1252
1252
1253 SKIPPED_PREFIX = b'skipped: '
1253 SKIPPED_PREFIX = b'skipped: '
1254 FAILED_PREFIX = b'hghave check failed: '
1254 FAILED_PREFIX = b'hghave check failed: '
1255 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1255 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1256
1256
1257 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1257 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1258 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1258 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1259 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1259 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1260
1260
1261 def __init__(self, path, *args, **kwds):
1261 def __init__(self, path, *args, **kwds):
1262 # accept an extra "case" parameter
1262 # accept an extra "case" parameter
1263 case = kwds.pop('case', [])
1263 case = kwds.pop('case', [])
1264 self._case = case
1264 self._case = case
1265 self._allcases = {x for y in parsettestcases(path) for x in y}
1265 self._allcases = {x for y in parsettestcases(path) for x in y}
1266 super(TTest, self).__init__(path, *args, **kwds)
1266 super(TTest, self).__init__(path, *args, **kwds)
1267 if case:
1267 if case:
1268 casepath = b'#'.join(case)
1268 casepath = b'#'.join(case)
1269 self.name = '%s#%s' % (self.name, _strpath(casepath))
1269 self.name = '%s#%s' % (self.name, _strpath(casepath))
1270 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1270 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1271 self._tmpname += b'-%s' % casepath
1271 self._tmpname += b'-%s' % casepath
1272 self._have = {}
1272 self._have = {}
1273
1273
1274 @property
1274 @property
1275 def refpath(self):
1275 def refpath(self):
1276 return os.path.join(self._testdir, self.bname)
1276 return os.path.join(self._testdir, self.bname)
1277
1277
1278 def _run(self, env):
1278 def _run(self, env):
1279 with open(self.path, 'rb') as f:
1279 with open(self.path, 'rb') as f:
1280 lines = f.readlines()
1280 lines = f.readlines()
1281
1281
1282 # .t file is both reference output and the test input, keep reference
1282 # .t file is both reference output and the test input, keep reference
1283 # output updated with the the test input. This avoids some race
1283 # output updated with the the test input. This avoids some race
1284 # conditions where the reference output does not match the actual test.
1284 # conditions where the reference output does not match the actual test.
1285 if self._refout is not None:
1285 if self._refout is not None:
1286 self._refout = lines
1286 self._refout = lines
1287
1287
1288 salt, script, after, expected = self._parsetest(lines)
1288 salt, script, after, expected = self._parsetest(lines)
1289
1289
1290 # Write out the generated script.
1290 # Write out the generated script.
1291 fname = b'%s.sh' % self._testtmp
1291 fname = b'%s.sh' % self._testtmp
1292 with open(fname, 'wb') as f:
1292 with open(fname, 'wb') as f:
1293 for l in script:
1293 for l in script:
1294 f.write(l)
1294 f.write(l)
1295
1295
1296 cmd = b'%s "%s"' % (self._shell, fname)
1296 cmd = b'%s "%s"' % (self._shell, fname)
1297 vlog("# Running", cmd)
1297 vlog("# Running", cmd)
1298
1298
1299 exitcode, output = self._runcommand(cmd, env)
1299 exitcode, output = self._runcommand(cmd, env)
1300
1300
1301 if self._aborted:
1301 if self._aborted:
1302 raise KeyboardInterrupt()
1302 raise KeyboardInterrupt()
1303
1303
1304 # Do not merge output if skipped. Return hghave message instead.
1304 # Do not merge output if skipped. Return hghave message instead.
1305 # Similarly, with --debug, output is None.
1305 # Similarly, with --debug, output is None.
1306 if exitcode == self.SKIPPED_STATUS or output is None:
1306 if exitcode == self.SKIPPED_STATUS or output is None:
1307 return exitcode, output
1307 return exitcode, output
1308
1308
1309 return self._processoutput(exitcode, output, salt, after, expected)
1309 return self._processoutput(exitcode, output, salt, after, expected)
1310
1310
1311 def _hghave(self, reqs):
1311 def _hghave(self, reqs):
1312 allreqs = b' '.join(reqs)
1312 allreqs = b' '.join(reqs)
1313 if allreqs in self._have:
1313 if allreqs in self._have:
1314 return self._have.get(allreqs)
1314 return self._have.get(allreqs)
1315
1315
1316 # TODO do something smarter when all other uses of hghave are gone.
1316 # TODO do something smarter when all other uses of hghave are gone.
1317 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1317 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1318 tdir = runtestdir.replace(b'\\', b'/')
1318 tdir = runtestdir.replace(b'\\', b'/')
1319 proc = Popen4(b'%s -c "%s/hghave %s"' %
1319 proc = Popen4(b'%s -c "%s/hghave %s"' %
1320 (self._shell, tdir, allreqs),
1320 (self._shell, tdir, allreqs),
1321 self._testtmp, 0, self._getenv())
1321 self._testtmp, 0, self._getenv())
1322 stdout, stderr = proc.communicate()
1322 stdout, stderr = proc.communicate()
1323 ret = proc.wait()
1323 ret = proc.wait()
1324 if wifexited(ret):
1324 if wifexited(ret):
1325 ret = os.WEXITSTATUS(ret)
1325 ret = os.WEXITSTATUS(ret)
1326 if ret == 2:
1326 if ret == 2:
1327 print(stdout.decode('utf-8'))
1327 print(stdout.decode('utf-8'))
1328 sys.exit(1)
1328 sys.exit(1)
1329
1329
1330 if ret != 0:
1330 if ret != 0:
1331 self._have[allreqs] = (False, stdout)
1331 self._have[allreqs] = (False, stdout)
1332 return False, stdout
1332 return False, stdout
1333
1333
1334 if b'slow' in reqs:
1334 if b'slow' in reqs:
1335 self._timeout = self._slowtimeout
1335 self._timeout = self._slowtimeout
1336
1336
1337 self._have[allreqs] = (True, None)
1337 self._have[allreqs] = (True, None)
1338 return True, None
1338 return True, None
1339
1339
1340 def _iftest(self, args):
1340 def _iftest(self, args):
1341 # implements "#if"
1341 # implements "#if"
1342 reqs = []
1342 reqs = []
1343 for arg in args:
1343 for arg in args:
1344 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1344 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1345 if arg[3:] in self._case:
1345 if arg[3:] in self._case:
1346 return False
1346 return False
1347 elif arg in self._allcases:
1347 elif arg in self._allcases:
1348 if arg not in self._case:
1348 if arg not in self._case:
1349 return False
1349 return False
1350 else:
1350 else:
1351 reqs.append(arg)
1351 reqs.append(arg)
1352 return self._hghave(reqs)[0]
1352 return self._hghave(reqs)[0]
1353
1353
1354 def _parsetest(self, lines):
1354 def _parsetest(self, lines):
1355 # We generate a shell script which outputs unique markers to line
1355 # We generate a shell script which outputs unique markers to line
1356 # up script results with our source. These markers include input
1356 # up script results with our source. These markers include input
1357 # line number and the last return code.
1357 # line number and the last return code.
1358 salt = b"SALT%d" % time.time()
1358 salt = b"SALT%d" % time.time()
1359 def addsalt(line, inpython):
1359 def addsalt(line, inpython):
1360 if inpython:
1360 if inpython:
1361 script.append(b'%s %d 0\n' % (salt, line))
1361 script.append(b'%s %d 0\n' % (salt, line))
1362 else:
1362 else:
1363 script.append(b'echo %s %d $?\n' % (salt, line))
1363 script.append(b'echo %s %d $?\n' % (salt, line))
1364 active = []
1364 active = []
1365 session = str(uuid.uuid4())
1365 session = str(uuid.uuid4())
1366 if PYTHON3:
1366 if PYTHON3:
1367 session = session.encode('ascii')
1367 session = session.encode('ascii')
1368 def toggletrace(cmd):
1368 def toggletrace(cmd):
1369 if isinstance(cmd, str):
1369 if isinstance(cmd, str):
1370 quoted = shellquote(cmd.strip())
1370 quoted = shellquote(cmd.strip())
1371 else:
1371 else:
1372 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1372 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1373 quoted = quoted.replace(b'\\', b'\\\\')
1373 quoted = quoted.replace(b'\\', b'\\\\')
1374 if active:
1374 if active:
1375 script.append(
1375 script.append(
1376 b'echo END %s %s >> "$HGCATAPULTSERVERPIPE"\n' % (
1376 b'echo END %s %s >> "$HGCATAPULTSERVERPIPE"\n' % (
1377 session, active[0]))
1377 session, active[0]))
1378 script.append(
1378 script.append(
1379 b'echo START %s %s >> "$HGCATAPULTSERVERPIPE"\n' % (
1379 b'echo START %s %s >> "$HGCATAPULTSERVERPIPE"\n' % (
1380 session, quoted))
1380 session, quoted))
1381 active[0:] = [quoted]
1381 active[0:] = [quoted]
1382
1382
1383 script = []
1383 script = []
1384
1384
1385 # After we run the shell script, we re-unify the script output
1385 # After we run the shell script, we re-unify the script output
1386 # with non-active parts of the source, with synchronization by our
1386 # with non-active parts of the source, with synchronization by our
1387 # SALT line number markers. The after table contains the non-active
1387 # SALT line number markers. The after table contains the non-active
1388 # components, ordered by line number.
1388 # components, ordered by line number.
1389 after = {}
1389 after = {}
1390
1390
1391 # Expected shell script output.
1391 # Expected shell script output.
1392 expected = {}
1392 expected = {}
1393
1393
1394 pos = prepos = -1
1394 pos = prepos = -1
1395
1395
1396 # True or False when in a true or false conditional section
1396 # True or False when in a true or false conditional section
1397 skipping = None
1397 skipping = None
1398
1398
1399 # We keep track of whether or not we're in a Python block so we
1399 # We keep track of whether or not we're in a Python block so we
1400 # can generate the surrounding doctest magic.
1400 # can generate the surrounding doctest magic.
1401 inpython = False
1401 inpython = False
1402
1402
1403 if self._debug:
1403 if self._debug:
1404 script.append(b'set -x\n')
1404 script.append(b'set -x\n')
1405 if self._hgcommand != b'hg':
1405 if self._hgcommand != b'hg':
1406 script.append(b'alias hg="%s"\n' % self._hgcommand)
1406 script.append(b'alias hg="%s"\n' % self._hgcommand)
1407 if os.getenv('MSYSTEM'):
1407 if os.getenv('MSYSTEM'):
1408 script.append(b'alias pwd="pwd -W"\n')
1408 script.append(b'alias pwd="pwd -W"\n')
1409
1409
1410 hgcatapult = os.getenv('HGCATAPULTSERVERPIPE')
1410 hgcatapult = os.getenv('HGCATAPULTSERVERPIPE')
1411 if hgcatapult and hgcatapult != os.devnull:
1411 if hgcatapult and hgcatapult != os.devnull:
1412 # Kludge: use a while loop to keep the pipe from getting
1412 # Kludge: use a while loop to keep the pipe from getting
1413 # closed by our echo commands. The still-running file gets
1413 # closed by our echo commands. The still-running file gets
1414 # reaped at the end of the script, which causes the while
1414 # reaped at the end of the script, which causes the while
1415 # loop to exit and closes the pipe. Sigh.
1415 # loop to exit and closes the pipe. Sigh.
1416 script.append(
1416 script.append(
1417 b'rtendtracing() {\n'
1417 b'rtendtracing() {\n'
1418 b' echo END %(session)s %(name)s >> $HGCATAPULTSERVERPIPE\n'
1418 b' echo END %(session)s %(name)s >> $HGCATAPULTSERVERPIPE\n'
1419 b' rm -f "$TESTTMP/.still-running"\n'
1419 b' rm -f "$TESTTMP/.still-running"\n'
1420 b'}\n'
1420 b'}\n'
1421 b'trap "rtendtracing" 0\n'
1421 b'trap "rtendtracing" 0\n'
1422 b'touch "$TESTTMP/.still-running"\n'
1422 b'touch "$TESTTMP/.still-running"\n'
1423 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1423 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1424 b'> $HGCATAPULTSERVERPIPE &\n'
1424 b'> $HGCATAPULTSERVERPIPE &\n'
1425 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1425 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1426 b'echo START %(session)s %(name)s >> $HGCATAPULTSERVERPIPE\n'
1426 b'echo START %(session)s %(name)s >> $HGCATAPULTSERVERPIPE\n'
1427 % {
1427 % {
1428 'name': self.name,
1428 'name': self.name,
1429 'session': session,
1429 'session': session,
1430 }
1430 }
1431 )
1431 )
1432
1432
1433 if self._case:
1433 if self._case:
1434 casestr = b'#'.join(self._case)
1434 casestr = b'#'.join(self._case)
1435 if isinstance(self._case, str):
1435 if isinstance(self._case, str):
1436 quoted = shellquote(casestr)
1436 quoted = shellquote(casestr)
1437 else:
1437 else:
1438 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1438 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1439 script.append(b'TESTCASE=%s\n' % quoted)
1439 script.append(b'TESTCASE=%s\n' % quoted)
1440 script.append(b'export TESTCASE\n')
1440 script.append(b'export TESTCASE\n')
1441
1441
1442 n = 0
1442 n = 0
1443 for n, l in enumerate(lines):
1443 for n, l in enumerate(lines):
1444 if not l.endswith(b'\n'):
1444 if not l.endswith(b'\n'):
1445 l += b'\n'
1445 l += b'\n'
1446 if l.startswith(b'#require'):
1446 if l.startswith(b'#require'):
1447 lsplit = l.split()
1447 lsplit = l.split()
1448 if len(lsplit) < 2 or lsplit[0] != b'#require':
1448 if len(lsplit) < 2 or lsplit[0] != b'#require':
1449 after.setdefault(pos, []).append(' !!! invalid #require\n')
1449 after.setdefault(pos, []).append(' !!! invalid #require\n')
1450 if not skipping:
1450 if not skipping:
1451 haveresult, message = self._hghave(lsplit[1:])
1451 haveresult, message = self._hghave(lsplit[1:])
1452 if not haveresult:
1452 if not haveresult:
1453 script = [b'echo "%s"\nexit 80\n' % message]
1453 script = [b'echo "%s"\nexit 80\n' % message]
1454 break
1454 break
1455 after.setdefault(pos, []).append(l)
1455 after.setdefault(pos, []).append(l)
1456 elif l.startswith(b'#if'):
1456 elif l.startswith(b'#if'):
1457 lsplit = l.split()
1457 lsplit = l.split()
1458 if len(lsplit) < 2 or lsplit[0] != b'#if':
1458 if len(lsplit) < 2 or lsplit[0] != b'#if':
1459 after.setdefault(pos, []).append(' !!! invalid #if\n')
1459 after.setdefault(pos, []).append(' !!! invalid #if\n')
1460 if skipping is not None:
1460 if skipping is not None:
1461 after.setdefault(pos, []).append(' !!! nested #if\n')
1461 after.setdefault(pos, []).append(' !!! nested #if\n')
1462 skipping = not self._iftest(lsplit[1:])
1462 skipping = not self._iftest(lsplit[1:])
1463 after.setdefault(pos, []).append(l)
1463 after.setdefault(pos, []).append(l)
1464 elif l.startswith(b'#else'):
1464 elif l.startswith(b'#else'):
1465 if skipping is None:
1465 if skipping is None:
1466 after.setdefault(pos, []).append(' !!! missing #if\n')
1466 after.setdefault(pos, []).append(' !!! missing #if\n')
1467 skipping = not skipping
1467 skipping = not skipping
1468 after.setdefault(pos, []).append(l)
1468 after.setdefault(pos, []).append(l)
1469 elif l.startswith(b'#endif'):
1469 elif l.startswith(b'#endif'):
1470 if skipping is None:
1470 if skipping is None:
1471 after.setdefault(pos, []).append(' !!! missing #if\n')
1471 after.setdefault(pos, []).append(' !!! missing #if\n')
1472 skipping = None
1472 skipping = None
1473 after.setdefault(pos, []).append(l)
1473 after.setdefault(pos, []).append(l)
1474 elif skipping:
1474 elif skipping:
1475 after.setdefault(pos, []).append(l)
1475 after.setdefault(pos, []).append(l)
1476 elif l.startswith(b' >>> '): # python inlines
1476 elif l.startswith(b' >>> '): # python inlines
1477 after.setdefault(pos, []).append(l)
1477 after.setdefault(pos, []).append(l)
1478 prepos = pos
1478 prepos = pos
1479 pos = n
1479 pos = n
1480 if not inpython:
1480 if not inpython:
1481 # We've just entered a Python block. Add the header.
1481 # We've just entered a Python block. Add the header.
1482 inpython = True
1482 inpython = True
1483 addsalt(prepos, False) # Make sure we report the exit code.
1483 addsalt(prepos, False) # Make sure we report the exit code.
1484 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1484 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1485 addsalt(n, True)
1485 addsalt(n, True)
1486 script.append(l[2:])
1486 script.append(l[2:])
1487 elif l.startswith(b' ... '): # python inlines
1487 elif l.startswith(b' ... '): # python inlines
1488 after.setdefault(prepos, []).append(l)
1488 after.setdefault(prepos, []).append(l)
1489 script.append(l[2:])
1489 script.append(l[2:])
1490 elif l.startswith(b' $ '): # commands
1490 elif l.startswith(b' $ '): # commands
1491 if inpython:
1491 if inpython:
1492 script.append(b'EOF\n')
1492 script.append(b'EOF\n')
1493 inpython = False
1493 inpython = False
1494 after.setdefault(pos, []).append(l)
1494 after.setdefault(pos, []).append(l)
1495 prepos = pos
1495 prepos = pos
1496 pos = n
1496 pos = n
1497 addsalt(n, False)
1497 addsalt(n, False)
1498 rawcmd = l[4:]
1498 rawcmd = l[4:]
1499 cmd = rawcmd.split()
1499 cmd = rawcmd.split()
1500 toggletrace(rawcmd)
1500 toggletrace(rawcmd)
1501 if len(cmd) == 2 and cmd[0] == b'cd':
1501 if len(cmd) == 2 and cmd[0] == b'cd':
1502 l = b' $ cd %s || exit 1\n' % cmd[1]
1502 l = b' $ cd %s || exit 1\n' % cmd[1]
1503 script.append(rawcmd)
1503 script.append(rawcmd)
1504 elif l.startswith(b' > '): # continuations
1504 elif l.startswith(b' > '): # continuations
1505 after.setdefault(prepos, []).append(l)
1505 after.setdefault(prepos, []).append(l)
1506 script.append(l[4:])
1506 script.append(l[4:])
1507 elif l.startswith(b' '): # results
1507 elif l.startswith(b' '): # results
1508 # Queue up a list of expected results.
1508 # Queue up a list of expected results.
1509 expected.setdefault(pos, []).append(l[2:])
1509 expected.setdefault(pos, []).append(l[2:])
1510 else:
1510 else:
1511 if inpython:
1511 if inpython:
1512 script.append(b'EOF\n')
1512 script.append(b'EOF\n')
1513 inpython = False
1513 inpython = False
1514 # Non-command/result. Queue up for merged output.
1514 # Non-command/result. Queue up for merged output.
1515 after.setdefault(pos, []).append(l)
1515 after.setdefault(pos, []).append(l)
1516
1516
1517 if inpython:
1517 if inpython:
1518 script.append(b'EOF\n')
1518 script.append(b'EOF\n')
1519 if skipping is not None:
1519 if skipping is not None:
1520 after.setdefault(pos, []).append(' !!! missing #endif\n')
1520 after.setdefault(pos, []).append(' !!! missing #endif\n')
1521 addsalt(n + 1, False)
1521 addsalt(n + 1, False)
1522 return salt, script, after, expected
1522 return salt, script, after, expected
1523
1523
1524 def _processoutput(self, exitcode, output, salt, after, expected):
1524 def _processoutput(self, exitcode, output, salt, after, expected):
1525 # Merge the script output back into a unified test.
1525 # Merge the script output back into a unified test.
1526 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1526 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1527 if exitcode != 0:
1527 if exitcode != 0:
1528 warnonly = 3
1528 warnonly = 3
1529
1529
1530 pos = -1
1530 pos = -1
1531 postout = []
1531 postout = []
1532 for l in output:
1532 for l in output:
1533 lout, lcmd = l, None
1533 lout, lcmd = l, None
1534 if salt in l:
1534 if salt in l:
1535 lout, lcmd = l.split(salt, 1)
1535 lout, lcmd = l.split(salt, 1)
1536
1536
1537 while lout:
1537 while lout:
1538 if not lout.endswith(b'\n'):
1538 if not lout.endswith(b'\n'):
1539 lout += b' (no-eol)\n'
1539 lout += b' (no-eol)\n'
1540
1540
1541 # Find the expected output at the current position.
1541 # Find the expected output at the current position.
1542 els = [None]
1542 els = [None]
1543 if expected.get(pos, None):
1543 if expected.get(pos, None):
1544 els = expected[pos]
1544 els = expected[pos]
1545
1545
1546 optional = []
1546 optional = []
1547 for i, el in enumerate(els):
1547 for i, el in enumerate(els):
1548 r = False
1548 r = False
1549 if el:
1549 if el:
1550 r, exact = self.linematch(el, lout)
1550 r, exact = self.linematch(el, lout)
1551 if isinstance(r, str):
1551 if isinstance(r, str):
1552 if r == '-glob':
1552 if r == '-glob':
1553 lout = ''.join(el.rsplit(' (glob)', 1))
1553 lout = ''.join(el.rsplit(' (glob)', 1))
1554 r = '' # Warn only this line.
1554 r = '' # Warn only this line.
1555 elif r == "retry":
1555 elif r == "retry":
1556 postout.append(b' ' + el)
1556 postout.append(b' ' + el)
1557 else:
1557 else:
1558 log('\ninfo, unknown linematch result: %r\n' % r)
1558 log('\ninfo, unknown linematch result: %r\n' % r)
1559 r = False
1559 r = False
1560 if r:
1560 if r:
1561 els.pop(i)
1561 els.pop(i)
1562 break
1562 break
1563 if el:
1563 if el:
1564 if el.endswith(b" (?)\n"):
1564 if el.endswith(b" (?)\n"):
1565 optional.append(i)
1565 optional.append(i)
1566 else:
1566 else:
1567 m = optline.match(el)
1567 m = optline.match(el)
1568 if m:
1568 if m:
1569 conditions = [
1569 conditions = [
1570 c for c in m.group(2).split(b' ')]
1570 c for c in m.group(2).split(b' ')]
1571
1571
1572 if not self._iftest(conditions):
1572 if not self._iftest(conditions):
1573 optional.append(i)
1573 optional.append(i)
1574 if exact:
1574 if exact:
1575 # Don't allow line to be matches against a later
1575 # Don't allow line to be matches against a later
1576 # line in the output
1576 # line in the output
1577 els.pop(i)
1577 els.pop(i)
1578 break
1578 break
1579
1579
1580 if r:
1580 if r:
1581 if r == "retry":
1581 if r == "retry":
1582 continue
1582 continue
1583 # clean up any optional leftovers
1583 # clean up any optional leftovers
1584 for i in optional:
1584 for i in optional:
1585 postout.append(b' ' + els[i])
1585 postout.append(b' ' + els[i])
1586 for i in reversed(optional):
1586 for i in reversed(optional):
1587 del els[i]
1587 del els[i]
1588 postout.append(b' ' + el)
1588 postout.append(b' ' + el)
1589 else:
1589 else:
1590 if self.NEEDESCAPE(lout):
1590 if self.NEEDESCAPE(lout):
1591 lout = TTest._stringescape(b'%s (esc)\n' %
1591 lout = TTest._stringescape(b'%s (esc)\n' %
1592 lout.rstrip(b'\n'))
1592 lout.rstrip(b'\n'))
1593 postout.append(b' ' + lout) # Let diff deal with it.
1593 postout.append(b' ' + lout) # Let diff deal with it.
1594 if r != '': # If line failed.
1594 if r != '': # If line failed.
1595 warnonly = 3 # for sure not
1595 warnonly = 3 # for sure not
1596 elif warnonly == 1: # Is "not yet" and line is warn only.
1596 elif warnonly == 1: # Is "not yet" and line is warn only.
1597 warnonly = 2 # Yes do warn.
1597 warnonly = 2 # Yes do warn.
1598 break
1598 break
1599 else:
1599 else:
1600 # clean up any optional leftovers
1600 # clean up any optional leftovers
1601 while expected.get(pos, None):
1601 while expected.get(pos, None):
1602 el = expected[pos].pop(0)
1602 el = expected[pos].pop(0)
1603 if el:
1603 if el:
1604 if not el.endswith(b" (?)\n"):
1604 if not el.endswith(b" (?)\n"):
1605 m = optline.match(el)
1605 m = optline.match(el)
1606 if m:
1606 if m:
1607 conditions = [c for c in m.group(2).split(b' ')]
1607 conditions = [c for c in m.group(2).split(b' ')]
1608
1608
1609 if self._iftest(conditions):
1609 if self._iftest(conditions):
1610 # Don't append as optional line
1610 # Don't append as optional line
1611 continue
1611 continue
1612 else:
1612 else:
1613 continue
1613 continue
1614 postout.append(b' ' + el)
1614 postout.append(b' ' + el)
1615
1615
1616 if lcmd:
1616 if lcmd:
1617 # Add on last return code.
1617 # Add on last return code.
1618 ret = int(lcmd.split()[1])
1618 ret = int(lcmd.split()[1])
1619 if ret != 0:
1619 if ret != 0:
1620 postout.append(b' [%d]\n' % ret)
1620 postout.append(b' [%d]\n' % ret)
1621 if pos in after:
1621 if pos in after:
1622 # Merge in non-active test bits.
1622 # Merge in non-active test bits.
1623 postout += after.pop(pos)
1623 postout += after.pop(pos)
1624 pos = int(lcmd.split()[0])
1624 pos = int(lcmd.split()[0])
1625
1625
1626 if pos in after:
1626 if pos in after:
1627 postout += after.pop(pos)
1627 postout += after.pop(pos)
1628
1628
1629 if warnonly == 2:
1629 if warnonly == 2:
1630 exitcode = False # Set exitcode to warned.
1630 exitcode = False # Set exitcode to warned.
1631
1631
1632 return exitcode, postout
1632 return exitcode, postout
1633
1633
1634 @staticmethod
1634 @staticmethod
1635 def rematch(el, l):
1635 def rematch(el, l):
1636 try:
1636 try:
1637 el = b'(?:' + el + b')'
1637 el = b'(?:' + el + b')'
1638 # use \Z to ensure that the regex matches to the end of the string
1638 # use \Z to ensure that the regex matches to the end of the string
1639 if os.name == 'nt':
1639 if os.name == 'nt':
1640 return re.match(el + br'\r?\n\Z', l)
1640 return re.match(el + br'\r?\n\Z', l)
1641 return re.match(el + br'\n\Z', l)
1641 return re.match(el + br'\n\Z', l)
1642 except re.error:
1642 except re.error:
1643 # el is an invalid regex
1643 # el is an invalid regex
1644 return False
1644 return False
1645
1645
1646 @staticmethod
1646 @staticmethod
1647 def globmatch(el, l):
1647 def globmatch(el, l):
1648 # The only supported special characters are * and ? plus / which also
1648 # The only supported special characters are * and ? plus / which also
1649 # matches \ on windows. Escaping of these characters is supported.
1649 # matches \ on windows. Escaping of these characters is supported.
1650 if el + b'\n' == l:
1650 if el + b'\n' == l:
1651 if os.altsep:
1651 if os.altsep:
1652 # matching on "/" is not needed for this line
1652 # matching on "/" is not needed for this line
1653 for pat in checkcodeglobpats:
1653 for pat in checkcodeglobpats:
1654 if pat.match(el):
1654 if pat.match(el):
1655 return True
1655 return True
1656 return b'-glob'
1656 return b'-glob'
1657 return True
1657 return True
1658 el = el.replace(b'$LOCALIP', b'*')
1658 el = el.replace(b'$LOCALIP', b'*')
1659 i, n = 0, len(el)
1659 i, n = 0, len(el)
1660 res = b''
1660 res = b''
1661 while i < n:
1661 while i < n:
1662 c = el[i:i + 1]
1662 c = el[i:i + 1]
1663 i += 1
1663 i += 1
1664 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1664 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1665 res += el[i - 1:i + 1]
1665 res += el[i - 1:i + 1]
1666 i += 1
1666 i += 1
1667 elif c == b'*':
1667 elif c == b'*':
1668 res += b'.*'
1668 res += b'.*'
1669 elif c == b'?':
1669 elif c == b'?':
1670 res += b'.'
1670 res += b'.'
1671 elif c == b'/' and os.altsep:
1671 elif c == b'/' and os.altsep:
1672 res += b'[/\\\\]'
1672 res += b'[/\\\\]'
1673 else:
1673 else:
1674 res += re.escape(c)
1674 res += re.escape(c)
1675 return TTest.rematch(res, l)
1675 return TTest.rematch(res, l)
1676
1676
1677 def linematch(self, el, l):
1677 def linematch(self, el, l):
1678 if el == l: # perfect match (fast)
1678 if el == l: # perfect match (fast)
1679 return True, True
1679 return True, True
1680 retry = False
1680 retry = False
1681 if el.endswith(b" (?)\n"):
1681 if el.endswith(b" (?)\n"):
1682 retry = "retry"
1682 retry = "retry"
1683 el = el[:-5] + b"\n"
1683 el = el[:-5] + b"\n"
1684 else:
1684 else:
1685 m = optline.match(el)
1685 m = optline.match(el)
1686 if m:
1686 if m:
1687 conditions = [c for c in m.group(2).split(b' ')]
1687 conditions = [c for c in m.group(2).split(b' ')]
1688
1688
1689 el = m.group(1) + b"\n"
1689 el = m.group(1) + b"\n"
1690 if not self._iftest(conditions):
1690 if not self._iftest(conditions):
1691 retry = "retry" # Not required by listed features
1691 retry = "retry" # Not required by listed features
1692
1692
1693 if el.endswith(b" (esc)\n"):
1693 if el.endswith(b" (esc)\n"):
1694 if PYTHON3:
1694 if PYTHON3:
1695 el = el[:-7].decode('unicode_escape') + '\n'
1695 el = el[:-7].decode('unicode_escape') + '\n'
1696 el = el.encode('utf-8')
1696 el = el.encode('utf-8')
1697 else:
1697 else:
1698 el = el[:-7].decode('string-escape') + '\n'
1698 el = el[:-7].decode('string-escape') + '\n'
1699 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1699 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1700 return True, True
1700 return True, True
1701 if el.endswith(b" (re)\n"):
1701 if el.endswith(b" (re)\n"):
1702 return (TTest.rematch(el[:-6], l) or retry), False
1702 return (TTest.rematch(el[:-6], l) or retry), False
1703 if el.endswith(b" (glob)\n"):
1703 if el.endswith(b" (glob)\n"):
1704 # ignore '(glob)' added to l by 'replacements'
1704 # ignore '(glob)' added to l by 'replacements'
1705 if l.endswith(b" (glob)\n"):
1705 if l.endswith(b" (glob)\n"):
1706 l = l[:-8] + b"\n"
1706 l = l[:-8] + b"\n"
1707 return (TTest.globmatch(el[:-8], l) or retry), False
1707 return (TTest.globmatch(el[:-8], l) or retry), False
1708 if os.altsep:
1708 if os.altsep:
1709 _l = l.replace(b'\\', b'/')
1709 _l = l.replace(b'\\', b'/')
1710 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
1710 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
1711 return True, True
1711 return True, True
1712 return retry, True
1712 return retry, True
1713
1713
1714 @staticmethod
1714 @staticmethod
1715 def parsehghaveoutput(lines):
1715 def parsehghaveoutput(lines):
1716 '''Parse hghave log lines.
1716 '''Parse hghave log lines.
1717
1717
1718 Return tuple of lists (missing, failed):
1718 Return tuple of lists (missing, failed):
1719 * the missing/unknown features
1719 * the missing/unknown features
1720 * the features for which existence check failed'''
1720 * the features for which existence check failed'''
1721 missing = []
1721 missing = []
1722 failed = []
1722 failed = []
1723 for line in lines:
1723 for line in lines:
1724 if line.startswith(TTest.SKIPPED_PREFIX):
1724 if line.startswith(TTest.SKIPPED_PREFIX):
1725 line = line.splitlines()[0]
1725 line = line.splitlines()[0]
1726 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1726 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1727 elif line.startswith(TTest.FAILED_PREFIX):
1727 elif line.startswith(TTest.FAILED_PREFIX):
1728 line = line.splitlines()[0]
1728 line = line.splitlines()[0]
1729 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1729 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1730
1730
1731 return missing, failed
1731 return missing, failed
1732
1732
1733 @staticmethod
1733 @staticmethod
1734 def _escapef(m):
1734 def _escapef(m):
1735 return TTest.ESCAPEMAP[m.group(0)]
1735 return TTest.ESCAPEMAP[m.group(0)]
1736
1736
1737 @staticmethod
1737 @staticmethod
1738 def _stringescape(s):
1738 def _stringescape(s):
1739 return TTest.ESCAPESUB(TTest._escapef, s)
1739 return TTest.ESCAPESUB(TTest._escapef, s)
1740
1740
1741 iolock = threading.RLock()
1741 iolock = threading.RLock()
1742 firstlock = threading.RLock()
1742 firstlock = threading.RLock()
1743 firsterror = False
1743 firsterror = False
1744
1744
1745 class TestResult(unittest._TextTestResult):
1745 class TestResult(unittest._TextTestResult):
1746 """Holds results when executing via unittest."""
1746 """Holds results when executing via unittest."""
1747 # Don't worry too much about accessing the non-public _TextTestResult.
1747 # Don't worry too much about accessing the non-public _TextTestResult.
1748 # It is relatively common in Python testing tools.
1748 # It is relatively common in Python testing tools.
1749 def __init__(self, options, *args, **kwargs):
1749 def __init__(self, options, *args, **kwargs):
1750 super(TestResult, self).__init__(*args, **kwargs)
1750 super(TestResult, self).__init__(*args, **kwargs)
1751
1751
1752 self._options = options
1752 self._options = options
1753
1753
1754 # unittest.TestResult didn't have skipped until 2.7. We need to
1754 # unittest.TestResult didn't have skipped until 2.7. We need to
1755 # polyfill it.
1755 # polyfill it.
1756 self.skipped = []
1756 self.skipped = []
1757
1757
1758 # We have a custom "ignored" result that isn't present in any Python
1758 # We have a custom "ignored" result that isn't present in any Python
1759 # unittest implementation. It is very similar to skipped. It may make
1759 # unittest implementation. It is very similar to skipped. It may make
1760 # sense to map it into skip some day.
1760 # sense to map it into skip some day.
1761 self.ignored = []
1761 self.ignored = []
1762
1762
1763 self.times = []
1763 self.times = []
1764 self._firststarttime = None
1764 self._firststarttime = None
1765 # Data stored for the benefit of generating xunit reports.
1765 # Data stored for the benefit of generating xunit reports.
1766 self.successes = []
1766 self.successes = []
1767 self.faildata = {}
1767 self.faildata = {}
1768
1768
1769 if options.color == 'auto':
1769 if options.color == 'auto':
1770 self.color = pygmentspresent and self.stream.isatty()
1770 self.color = pygmentspresent and self.stream.isatty()
1771 elif options.color == 'never':
1771 elif options.color == 'never':
1772 self.color = False
1772 self.color = False
1773 else: # 'always', for testing purposes
1773 else: # 'always', for testing purposes
1774 self.color = pygmentspresent
1774 self.color = pygmentspresent
1775
1775
1776 def onStart(self, test):
1776 def onStart(self, test):
1777 """ Can be overriden by custom TestResult
1777 """ Can be overriden by custom TestResult
1778 """
1778 """
1779
1779
1780 def onEnd(self):
1780 def onEnd(self):
1781 """ Can be overriden by custom TestResult
1781 """ Can be overriden by custom TestResult
1782 """
1782 """
1783
1783
1784 def addFailure(self, test, reason):
1784 def addFailure(self, test, reason):
1785 self.failures.append((test, reason))
1785 self.failures.append((test, reason))
1786
1786
1787 if self._options.first:
1787 if self._options.first:
1788 self.stop()
1788 self.stop()
1789 else:
1789 else:
1790 with iolock:
1790 with iolock:
1791 if reason == "timed out":
1791 if reason == "timed out":
1792 self.stream.write('t')
1792 self.stream.write('t')
1793 else:
1793 else:
1794 if not self._options.nodiff:
1794 if not self._options.nodiff:
1795 self.stream.write('\n')
1795 self.stream.write('\n')
1796 # Exclude the '\n' from highlighting to lex correctly
1796 # Exclude the '\n' from highlighting to lex correctly
1797 formatted = 'ERROR: %s output changed\n' % test
1797 formatted = 'ERROR: %s output changed\n' % test
1798 self.stream.write(highlightmsg(formatted, self.color))
1798 self.stream.write(highlightmsg(formatted, self.color))
1799 self.stream.write('!')
1799 self.stream.write('!')
1800
1800
1801 self.stream.flush()
1801 self.stream.flush()
1802
1802
1803 def addSuccess(self, test):
1803 def addSuccess(self, test):
1804 with iolock:
1804 with iolock:
1805 super(TestResult, self).addSuccess(test)
1805 super(TestResult, self).addSuccess(test)
1806 self.successes.append(test)
1806 self.successes.append(test)
1807
1807
1808 def addError(self, test, err):
1808 def addError(self, test, err):
1809 super(TestResult, self).addError(test, err)
1809 super(TestResult, self).addError(test, err)
1810 if self._options.first:
1810 if self._options.first:
1811 self.stop()
1811 self.stop()
1812
1812
1813 # Polyfill.
1813 # Polyfill.
1814 def addSkip(self, test, reason):
1814 def addSkip(self, test, reason):
1815 self.skipped.append((test, reason))
1815 self.skipped.append((test, reason))
1816 with iolock:
1816 with iolock:
1817 if self.showAll:
1817 if self.showAll:
1818 self.stream.writeln('skipped %s' % reason)
1818 self.stream.writeln('skipped %s' % reason)
1819 else:
1819 else:
1820 self.stream.write('s')
1820 self.stream.write('s')
1821 self.stream.flush()
1821 self.stream.flush()
1822
1822
1823 def addIgnore(self, test, reason):
1823 def addIgnore(self, test, reason):
1824 self.ignored.append((test, reason))
1824 self.ignored.append((test, reason))
1825 with iolock:
1825 with iolock:
1826 if self.showAll:
1826 if self.showAll:
1827 self.stream.writeln('ignored %s' % reason)
1827 self.stream.writeln('ignored %s' % reason)
1828 else:
1828 else:
1829 if reason not in ('not retesting', "doesn't match keyword"):
1829 if reason not in ('not retesting', "doesn't match keyword"):
1830 self.stream.write('i')
1830 self.stream.write('i')
1831 else:
1831 else:
1832 self.testsRun += 1
1832 self.testsRun += 1
1833 self.stream.flush()
1833 self.stream.flush()
1834
1834
1835 def addOutputMismatch(self, test, ret, got, expected):
1835 def addOutputMismatch(self, test, ret, got, expected):
1836 """Record a mismatch in test output for a particular test."""
1836 """Record a mismatch in test output for a particular test."""
1837 if self.shouldStop or firsterror:
1837 if self.shouldStop or firsterror:
1838 # don't print, some other test case already failed and
1838 # don't print, some other test case already failed and
1839 # printed, we're just stale and probably failed due to our
1839 # printed, we're just stale and probably failed due to our
1840 # temp dir getting cleaned up.
1840 # temp dir getting cleaned up.
1841 return
1841 return
1842
1842
1843 accepted = False
1843 accepted = False
1844 lines = []
1844 lines = []
1845
1845
1846 with iolock:
1846 with iolock:
1847 if self._options.nodiff:
1847 if self._options.nodiff:
1848 pass
1848 pass
1849 elif self._options.view:
1849 elif self._options.view:
1850 v = self._options.view
1850 v = self._options.view
1851 os.system(r"%s %s %s" %
1851 os.system(r"%s %s %s" %
1852 (v, _strpath(test.refpath), _strpath(test.errpath)))
1852 (v, _strpath(test.refpath), _strpath(test.errpath)))
1853 else:
1853 else:
1854 servefail, lines = getdiff(expected, got,
1854 servefail, lines = getdiff(expected, got,
1855 test.refpath, test.errpath)
1855 test.refpath, test.errpath)
1856 self.stream.write('\n')
1856 self.stream.write('\n')
1857 for line in lines:
1857 for line in lines:
1858 line = highlightdiff(line, self.color)
1858 line = highlightdiff(line, self.color)
1859 if PYTHON3:
1859 if PYTHON3:
1860 self.stream.flush()
1860 self.stream.flush()
1861 self.stream.buffer.write(line)
1861 self.stream.buffer.write(line)
1862 self.stream.buffer.flush()
1862 self.stream.buffer.flush()
1863 else:
1863 else:
1864 self.stream.write(line)
1864 self.stream.write(line)
1865 self.stream.flush()
1865 self.stream.flush()
1866
1866
1867 if servefail:
1867 if servefail:
1868 raise test.failureException(
1868 raise test.failureException(
1869 'server failed to start (HGPORT=%s)' % test._startport)
1869 'server failed to start (HGPORT=%s)' % test._startport)
1870
1870
1871 # handle interactive prompt without releasing iolock
1871 # handle interactive prompt without releasing iolock
1872 if self._options.interactive:
1872 if self._options.interactive:
1873 if test.readrefout() != expected:
1873 if test.readrefout() != expected:
1874 self.stream.write(
1874 self.stream.write(
1875 'Reference output has changed (run again to prompt '
1875 'Reference output has changed (run again to prompt '
1876 'changes)')
1876 'changes)')
1877 else:
1877 else:
1878 self.stream.write('Accept this change? [n] ')
1878 self.stream.write('Accept this change? [n] ')
1879 self.stream.flush()
1879 self.stream.flush()
1880 answer = sys.stdin.readline().strip()
1880 answer = sys.stdin.readline().strip()
1881 if answer.lower() in ('y', 'yes'):
1881 if answer.lower() in ('y', 'yes'):
1882 if test.path.endswith(b'.t'):
1882 if test.path.endswith(b'.t'):
1883 rename(test.errpath, test.path)
1883 rename(test.errpath, test.path)
1884 else:
1884 else:
1885 rename(test.errpath, '%s.out' % test.path)
1885 rename(test.errpath, '%s.out' % test.path)
1886 accepted = True
1886 accepted = True
1887 if not accepted:
1887 if not accepted:
1888 self.faildata[test.name] = b''.join(lines)
1888 self.faildata[test.name] = b''.join(lines)
1889
1889
1890 return accepted
1890 return accepted
1891
1891
1892 def startTest(self, test):
1892 def startTest(self, test):
1893 super(TestResult, self).startTest(test)
1893 super(TestResult, self).startTest(test)
1894
1894
1895 # os.times module computes the user time and system time spent by
1895 # os.times module computes the user time and system time spent by
1896 # child's processes along with real elapsed time taken by a process.
1896 # child's processes along with real elapsed time taken by a process.
1897 # This module has one limitation. It can only work for Linux user
1897 # This module has one limitation. It can only work for Linux user
1898 # and not for Windows.
1898 # and not for Windows.
1899 test.started = os.times()
1899 test.started = os.times()
1900 if self._firststarttime is None: # thread racy but irrelevant
1900 if self._firststarttime is None: # thread racy but irrelevant
1901 self._firststarttime = test.started[4]
1901 self._firststarttime = test.started[4]
1902
1902
1903 def stopTest(self, test, interrupted=False):
1903 def stopTest(self, test, interrupted=False):
1904 super(TestResult, self).stopTest(test)
1904 super(TestResult, self).stopTest(test)
1905
1905
1906 test.stopped = os.times()
1906 test.stopped = os.times()
1907
1907
1908 starttime = test.started
1908 starttime = test.started
1909 endtime = test.stopped
1909 endtime = test.stopped
1910 origin = self._firststarttime
1910 origin = self._firststarttime
1911 self.times.append((test.name,
1911 self.times.append((test.name,
1912 endtime[2] - starttime[2], # user space CPU time
1912 endtime[2] - starttime[2], # user space CPU time
1913 endtime[3] - starttime[3], # sys space CPU time
1913 endtime[3] - starttime[3], # sys space CPU time
1914 endtime[4] - starttime[4], # real time
1914 endtime[4] - starttime[4], # real time
1915 starttime[4] - origin, # start date in run context
1915 starttime[4] - origin, # start date in run context
1916 endtime[4] - origin, # end date in run context
1916 endtime[4] - origin, # end date in run context
1917 ))
1917 ))
1918
1918
1919 if interrupted:
1919 if interrupted:
1920 with iolock:
1920 with iolock:
1921 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1921 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1922 test.name, self.times[-1][3]))
1922 test.name, self.times[-1][3]))
1923
1923
1924 def getTestResult():
1924 def getTestResult():
1925 """
1925 """
1926 Returns the relevant test result
1926 Returns the relevant test result
1927 """
1927 """
1928 if "CUSTOM_TEST_RESULT" in os.environ:
1928 if "CUSTOM_TEST_RESULT" in os.environ:
1929 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
1929 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
1930 return testresultmodule.TestResult
1930 return testresultmodule.TestResult
1931 else:
1931 else:
1932 return TestResult
1932 return TestResult
1933
1933
1934 class TestSuite(unittest.TestSuite):
1934 class TestSuite(unittest.TestSuite):
1935 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1935 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1936
1936
1937 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1937 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1938 retest=False, keywords=None, loop=False, runs_per_test=1,
1938 retest=False, keywords=None, loop=False, runs_per_test=1,
1939 loadtest=None, showchannels=False,
1939 loadtest=None, showchannels=False,
1940 *args, **kwargs):
1940 *args, **kwargs):
1941 """Create a new instance that can run tests with a configuration.
1941 """Create a new instance that can run tests with a configuration.
1942
1942
1943 testdir specifies the directory where tests are executed from. This
1943 testdir specifies the directory where tests are executed from. This
1944 is typically the ``tests`` directory from Mercurial's source
1944 is typically the ``tests`` directory from Mercurial's source
1945 repository.
1945 repository.
1946
1946
1947 jobs specifies the number of jobs to run concurrently. Each test
1947 jobs specifies the number of jobs to run concurrently. Each test
1948 executes on its own thread. Tests actually spawn new processes, so
1948 executes on its own thread. Tests actually spawn new processes, so
1949 state mutation should not be an issue.
1949 state mutation should not be an issue.
1950
1950
1951 If there is only one job, it will use the main thread.
1951 If there is only one job, it will use the main thread.
1952
1952
1953 whitelist and blacklist denote tests that have been whitelisted and
1953 whitelist and blacklist denote tests that have been whitelisted and
1954 blacklisted, respectively. These arguments don't belong in TestSuite.
1954 blacklisted, respectively. These arguments don't belong in TestSuite.
1955 Instead, whitelist and blacklist should be handled by the thing that
1955 Instead, whitelist and blacklist should be handled by the thing that
1956 populates the TestSuite with tests. They are present to preserve
1956 populates the TestSuite with tests. They are present to preserve
1957 backwards compatible behavior which reports skipped tests as part
1957 backwards compatible behavior which reports skipped tests as part
1958 of the results.
1958 of the results.
1959
1959
1960 retest denotes whether to retest failed tests. This arguably belongs
1960 retest denotes whether to retest failed tests. This arguably belongs
1961 outside of TestSuite.
1961 outside of TestSuite.
1962
1962
1963 keywords denotes key words that will be used to filter which tests
1963 keywords denotes key words that will be used to filter which tests
1964 to execute. This arguably belongs outside of TestSuite.
1964 to execute. This arguably belongs outside of TestSuite.
1965
1965
1966 loop denotes whether to loop over tests forever.
1966 loop denotes whether to loop over tests forever.
1967 """
1967 """
1968 super(TestSuite, self).__init__(*args, **kwargs)
1968 super(TestSuite, self).__init__(*args, **kwargs)
1969
1969
1970 self._jobs = jobs
1970 self._jobs = jobs
1971 self._whitelist = whitelist
1971 self._whitelist = whitelist
1972 self._blacklist = blacklist
1972 self._blacklist = blacklist
1973 self._retest = retest
1973 self._retest = retest
1974 self._keywords = keywords
1974 self._keywords = keywords
1975 self._loop = loop
1975 self._loop = loop
1976 self._runs_per_test = runs_per_test
1976 self._runs_per_test = runs_per_test
1977 self._loadtest = loadtest
1977 self._loadtest = loadtest
1978 self._showchannels = showchannels
1978 self._showchannels = showchannels
1979
1979
1980 def run(self, result):
1980 def run(self, result):
1981 # We have a number of filters that need to be applied. We do this
1981 # We have a number of filters that need to be applied. We do this
1982 # here instead of inside Test because it makes the running logic for
1982 # here instead of inside Test because it makes the running logic for
1983 # Test simpler.
1983 # Test simpler.
1984 tests = []
1984 tests = []
1985 num_tests = [0]
1985 num_tests = [0]
1986 for test in self._tests:
1986 for test in self._tests:
1987 def get():
1987 def get():
1988 num_tests[0] += 1
1988 num_tests[0] += 1
1989 if getattr(test, 'should_reload', False):
1989 if getattr(test, 'should_reload', False):
1990 return self._loadtest(test, num_tests[0])
1990 return self._loadtest(test, num_tests[0])
1991 return test
1991 return test
1992 if not os.path.exists(test.path):
1992 if not os.path.exists(test.path):
1993 result.addSkip(test, "Doesn't exist")
1993 result.addSkip(test, "Doesn't exist")
1994 continue
1994 continue
1995
1995
1996 if not (self._whitelist and test.bname in self._whitelist):
1996 if not (self._whitelist and test.bname in self._whitelist):
1997 if self._blacklist and test.bname in self._blacklist:
1997 if self._blacklist and test.bname in self._blacklist:
1998 result.addSkip(test, 'blacklisted')
1998 result.addSkip(test, 'blacklisted')
1999 continue
1999 continue
2000
2000
2001 if self._retest and not os.path.exists(test.errpath):
2001 if self._retest and not os.path.exists(test.errpath):
2002 result.addIgnore(test, 'not retesting')
2002 result.addIgnore(test, 'not retesting')
2003 continue
2003 continue
2004
2004
2005 if self._keywords:
2005 if self._keywords:
2006 with open(test.path, 'rb') as f:
2006 with open(test.path, 'rb') as f:
2007 t = f.read().lower() + test.bname.lower()
2007 t = f.read().lower() + test.bname.lower()
2008 ignored = False
2008 ignored = False
2009 for k in self._keywords.lower().split():
2009 for k in self._keywords.lower().split():
2010 if k not in t:
2010 if k not in t:
2011 result.addIgnore(test, "doesn't match keyword")
2011 result.addIgnore(test, "doesn't match keyword")
2012 ignored = True
2012 ignored = True
2013 break
2013 break
2014
2014
2015 if ignored:
2015 if ignored:
2016 continue
2016 continue
2017 for _ in xrange(self._runs_per_test):
2017 for _ in xrange(self._runs_per_test):
2018 tests.append(get())
2018 tests.append(get())
2019
2019
2020 runtests = list(tests)
2020 runtests = list(tests)
2021 done = queue.Queue()
2021 done = queue.Queue()
2022 running = 0
2022 running = 0
2023
2023
2024 channels = [""] * self._jobs
2024 channels = [""] * self._jobs
2025
2025
2026 def job(test, result):
2026 def job(test, result):
2027 for n, v in enumerate(channels):
2027 for n, v in enumerate(channels):
2028 if not v:
2028 if not v:
2029 channel = n
2029 channel = n
2030 break
2030 break
2031 else:
2031 else:
2032 raise ValueError('Could not find output channel')
2032 raise ValueError('Could not find output channel')
2033 channels[channel] = "=" + test.name[5:].split(".")[0]
2033 channels[channel] = "=" + test.name[5:].split(".")[0]
2034 try:
2034 try:
2035 test(result)
2035 test(result)
2036 done.put(None)
2036 done.put(None)
2037 except KeyboardInterrupt:
2037 except KeyboardInterrupt:
2038 pass
2038 pass
2039 except: # re-raises
2039 except: # re-raises
2040 done.put(('!', test, 'run-test raised an error, see traceback'))
2040 done.put(('!', test, 'run-test raised an error, see traceback'))
2041 raise
2041 raise
2042 finally:
2042 finally:
2043 try:
2043 try:
2044 channels[channel] = ''
2044 channels[channel] = ''
2045 except IndexError:
2045 except IndexError:
2046 pass
2046 pass
2047
2047
2048 def stat():
2048 def stat():
2049 count = 0
2049 count = 0
2050 while channels:
2050 while channels:
2051 d = '\n%03s ' % count
2051 d = '\n%03s ' % count
2052 for n, v in enumerate(channels):
2052 for n, v in enumerate(channels):
2053 if v:
2053 if v:
2054 d += v[0]
2054 d += v[0]
2055 channels[n] = v[1:] or '.'
2055 channels[n] = v[1:] or '.'
2056 else:
2056 else:
2057 d += ' '
2057 d += ' '
2058 d += ' '
2058 d += ' '
2059 with iolock:
2059 with iolock:
2060 sys.stdout.write(d + ' ')
2060 sys.stdout.write(d + ' ')
2061 sys.stdout.flush()
2061 sys.stdout.flush()
2062 for x in xrange(10):
2062 for x in xrange(10):
2063 if channels:
2063 if channels:
2064 time.sleep(.1)
2064 time.sleep(.1)
2065 count += 1
2065 count += 1
2066
2066
2067 stoppedearly = False
2067 stoppedearly = False
2068
2068
2069 if self._showchannels:
2069 if self._showchannels:
2070 statthread = threading.Thread(target=stat, name="stat")
2070 statthread = threading.Thread(target=stat, name="stat")
2071 statthread.start()
2071 statthread.start()
2072
2072
2073 try:
2073 try:
2074 while tests or running:
2074 while tests or running:
2075 if not done.empty() or running == self._jobs or not tests:
2075 if not done.empty() or running == self._jobs or not tests:
2076 try:
2076 try:
2077 done.get(True, 1)
2077 done.get(True, 1)
2078 running -= 1
2078 running -= 1
2079 if result and result.shouldStop:
2079 if result and result.shouldStop:
2080 stoppedearly = True
2080 stoppedearly = True
2081 break
2081 break
2082 except queue.Empty:
2082 except queue.Empty:
2083 continue
2083 continue
2084 if tests and not running == self._jobs:
2084 if tests and not running == self._jobs:
2085 test = tests.pop(0)
2085 test = tests.pop(0)
2086 if self._loop:
2086 if self._loop:
2087 if getattr(test, 'should_reload', False):
2087 if getattr(test, 'should_reload', False):
2088 num_tests[0] += 1
2088 num_tests[0] += 1
2089 tests.append(
2089 tests.append(
2090 self._loadtest(test, num_tests[0]))
2090 self._loadtest(test, num_tests[0]))
2091 else:
2091 else:
2092 tests.append(test)
2092 tests.append(test)
2093 if self._jobs == 1:
2093 if self._jobs == 1:
2094 job(test, result)
2094 job(test, result)
2095 else:
2095 else:
2096 t = threading.Thread(target=job, name=test.name,
2096 t = threading.Thread(target=job, name=test.name,
2097 args=(test, result))
2097 args=(test, result))
2098 t.start()
2098 t.start()
2099 running += 1
2099 running += 1
2100
2100
2101 # If we stop early we still need to wait on started tests to
2101 # If we stop early we still need to wait on started tests to
2102 # finish. Otherwise, there is a race between the test completing
2102 # finish. Otherwise, there is a race between the test completing
2103 # and the test's cleanup code running. This could result in the
2103 # and the test's cleanup code running. This could result in the
2104 # test reporting incorrect.
2104 # test reporting incorrect.
2105 if stoppedearly:
2105 if stoppedearly:
2106 while running:
2106 while running:
2107 try:
2107 try:
2108 done.get(True, 1)
2108 done.get(True, 1)
2109 running -= 1
2109 running -= 1
2110 except queue.Empty:
2110 except queue.Empty:
2111 continue
2111 continue
2112 except KeyboardInterrupt:
2112 except KeyboardInterrupt:
2113 for test in runtests:
2113 for test in runtests:
2114 test.abort()
2114 test.abort()
2115
2115
2116 channels = []
2116 channels = []
2117
2117
2118 return result
2118 return result
2119
2119
2120 # Save the most recent 5 wall-clock runtimes of each test to a
2120 # Save the most recent 5 wall-clock runtimes of each test to a
2121 # human-readable text file named .testtimes. Tests are sorted
2121 # human-readable text file named .testtimes. Tests are sorted
2122 # alphabetically, while times for each test are listed from oldest to
2122 # alphabetically, while times for each test are listed from oldest to
2123 # newest.
2123 # newest.
2124
2124
2125 def loadtimes(outputdir):
2125 def loadtimes(outputdir):
2126 times = []
2126 times = []
2127 try:
2127 try:
2128 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2128 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2129 for line in fp:
2129 for line in fp:
2130 m = re.match('(.*?) ([0-9. ]+)', line)
2130 m = re.match('(.*?) ([0-9. ]+)', line)
2131 times.append((m.group(1),
2131 times.append((m.group(1),
2132 [float(t) for t in m.group(2).split()]))
2132 [float(t) for t in m.group(2).split()]))
2133 except IOError as err:
2133 except IOError as err:
2134 if err.errno != errno.ENOENT:
2134 if err.errno != errno.ENOENT:
2135 raise
2135 raise
2136 return times
2136 return times
2137
2137
2138 def savetimes(outputdir, result):
2138 def savetimes(outputdir, result):
2139 saved = dict(loadtimes(outputdir))
2139 saved = dict(loadtimes(outputdir))
2140 maxruns = 5
2140 maxruns = 5
2141 skipped = set([str(t[0]) for t in result.skipped])
2141 skipped = set([str(t[0]) for t in result.skipped])
2142 for tdata in result.times:
2142 for tdata in result.times:
2143 test, real = tdata[0], tdata[3]
2143 test, real = tdata[0], tdata[3]
2144 if test not in skipped:
2144 if test not in skipped:
2145 ts = saved.setdefault(test, [])
2145 ts = saved.setdefault(test, [])
2146 ts.append(real)
2146 ts.append(real)
2147 ts[:] = ts[-maxruns:]
2147 ts[:] = ts[-maxruns:]
2148
2148
2149 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
2149 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
2150 dir=outputdir, text=True)
2150 dir=outputdir, text=True)
2151 with os.fdopen(fd, 'w') as fp:
2151 with os.fdopen(fd, 'w') as fp:
2152 for name, ts in sorted(saved.items()):
2152 for name, ts in sorted(saved.items()):
2153 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2153 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2154 timepath = os.path.join(outputdir, b'.testtimes')
2154 timepath = os.path.join(outputdir, b'.testtimes')
2155 try:
2155 try:
2156 os.unlink(timepath)
2156 os.unlink(timepath)
2157 except OSError:
2157 except OSError:
2158 pass
2158 pass
2159 try:
2159 try:
2160 os.rename(tmpname, timepath)
2160 os.rename(tmpname, timepath)
2161 except OSError:
2161 except OSError:
2162 pass
2162 pass
2163
2163
2164 class TextTestRunner(unittest.TextTestRunner):
2164 class TextTestRunner(unittest.TextTestRunner):
2165 """Custom unittest test runner that uses appropriate settings."""
2165 """Custom unittest test runner that uses appropriate settings."""
2166
2166
2167 def __init__(self, runner, *args, **kwargs):
2167 def __init__(self, runner, *args, **kwargs):
2168 super(TextTestRunner, self).__init__(*args, **kwargs)
2168 super(TextTestRunner, self).__init__(*args, **kwargs)
2169
2169
2170 self._runner = runner
2170 self._runner = runner
2171
2171
2172 self._result = getTestResult()(self._runner.options, self.stream,
2172 self._result = getTestResult()(self._runner.options, self.stream,
2173 self.descriptions, self.verbosity)
2173 self.descriptions, self.verbosity)
2174
2174
2175 def listtests(self, test):
2175 def listtests(self, test):
2176 test = sorted(test, key=lambda t: t.name)
2176 test = sorted(test, key=lambda t: t.name)
2177
2177
2178 self._result.onStart(test)
2178 self._result.onStart(test)
2179
2179
2180 for t in test:
2180 for t in test:
2181 print(t.name)
2181 print(t.name)
2182 self._result.addSuccess(t)
2182 self._result.addSuccess(t)
2183
2183
2184 if self._runner.options.xunit:
2184 if self._runner.options.xunit:
2185 with open(self._runner.options.xunit, "wb") as xuf:
2185 with open(self._runner.options.xunit, "wb") as xuf:
2186 self._writexunit(self._result, xuf)
2186 self._writexunit(self._result, xuf)
2187
2187
2188 if self._runner.options.json:
2188 if self._runner.options.json:
2189 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2189 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2190 with open(jsonpath, 'w') as fp:
2190 with open(jsonpath, 'w') as fp:
2191 self._writejson(self._result, fp)
2191 self._writejson(self._result, fp)
2192
2192
2193 return self._result
2193 return self._result
2194
2194
2195 def run(self, test):
2195 def run(self, test):
2196 self._result.onStart(test)
2196 self._result.onStart(test)
2197 test(self._result)
2197 test(self._result)
2198
2198
2199 failed = len(self._result.failures)
2199 failed = len(self._result.failures)
2200 skipped = len(self._result.skipped)
2200 skipped = len(self._result.skipped)
2201 ignored = len(self._result.ignored)
2201 ignored = len(self._result.ignored)
2202
2202
2203 with iolock:
2203 with iolock:
2204 self.stream.writeln('')
2204 self.stream.writeln('')
2205
2205
2206 if not self._runner.options.noskips:
2206 if not self._runner.options.noskips:
2207 for test, msg in self._result.skipped:
2207 for test, msg in self._result.skipped:
2208 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2208 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2209 msg = highlightmsg(formatted, self._result.color)
2209 msg = highlightmsg(formatted, self._result.color)
2210 self.stream.write(msg)
2210 self.stream.write(msg)
2211 for test, msg in self._result.failures:
2211 for test, msg in self._result.failures:
2212 formatted = 'Failed %s: %s\n' % (test.name, msg)
2212 formatted = 'Failed %s: %s\n' % (test.name, msg)
2213 self.stream.write(highlightmsg(formatted, self._result.color))
2213 self.stream.write(highlightmsg(formatted, self._result.color))
2214 for test, msg in self._result.errors:
2214 for test, msg in self._result.errors:
2215 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2215 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2216
2216
2217 if self._runner.options.xunit:
2217 if self._runner.options.xunit:
2218 with open(self._runner.options.xunit, "wb") as xuf:
2218 with open(self._runner.options.xunit, "wb") as xuf:
2219 self._writexunit(self._result, xuf)
2219 self._writexunit(self._result, xuf)
2220
2220
2221 if self._runner.options.json:
2221 if self._runner.options.json:
2222 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2222 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2223 with open(jsonpath, 'w') as fp:
2223 with open(jsonpath, 'w') as fp:
2224 self._writejson(self._result, fp)
2224 self._writejson(self._result, fp)
2225
2225
2226 self._runner._checkhglib('Tested')
2226 self._runner._checkhglib('Tested')
2227
2227
2228 savetimes(self._runner._outputdir, self._result)
2228 savetimes(self._runner._outputdir, self._result)
2229
2229
2230 if failed and self._runner.options.known_good_rev:
2230 if failed and self._runner.options.known_good_rev:
2231 self._bisecttests(t for t, m in self._result.failures)
2231 self._bisecttests(t for t, m in self._result.failures)
2232 self.stream.writeln(
2232 self.stream.writeln(
2233 '# Ran %d tests, %d skipped, %d failed.'
2233 '# Ran %d tests, %d skipped, %d failed.'
2234 % (self._result.testsRun, skipped + ignored, failed))
2234 % (self._result.testsRun, skipped + ignored, failed))
2235 if failed:
2235 if failed:
2236 self.stream.writeln('python hash seed: %s' %
2236 self.stream.writeln('python hash seed: %s' %
2237 os.environ['PYTHONHASHSEED'])
2237 os.environ['PYTHONHASHSEED'])
2238 if self._runner.options.time:
2238 if self._runner.options.time:
2239 self.printtimes(self._result.times)
2239 self.printtimes(self._result.times)
2240
2240
2241 if self._runner.options.exceptions:
2241 if self._runner.options.exceptions:
2242 exceptions = aggregateexceptions(
2242 exceptions = aggregateexceptions(
2243 os.path.join(self._runner._outputdir, b'exceptions'))
2243 os.path.join(self._runner._outputdir, b'exceptions'))
2244
2244
2245 self.stream.writeln('Exceptions Report:')
2245 self.stream.writeln('Exceptions Report:')
2246 self.stream.writeln('%d total from %d frames' %
2246 self.stream.writeln('%d total from %d frames' %
2247 (exceptions['total'],
2247 (exceptions['total'],
2248 len(exceptions['exceptioncounts'])))
2248 len(exceptions['exceptioncounts'])))
2249 combined = exceptions['combined']
2249 combined = exceptions['combined']
2250 for key in sorted(combined, key=combined.get, reverse=True):
2250 for key in sorted(combined, key=combined.get, reverse=True):
2251 frame, line, exc = key
2251 frame, line, exc = key
2252 totalcount, testcount, leastcount, leasttest = combined[key]
2252 totalcount, testcount, leastcount, leasttest = combined[key]
2253
2253
2254 self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
2254 self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
2255 % (totalcount,
2255 % (totalcount,
2256 testcount,
2256 testcount,
2257 frame, exc,
2257 frame, exc,
2258 leasttest, leastcount))
2258 leasttest, leastcount))
2259
2259
2260 self.stream.flush()
2260 self.stream.flush()
2261
2261
2262 return self._result
2262 return self._result
2263
2263
2264 def _bisecttests(self, tests):
2264 def _bisecttests(self, tests):
2265 bisectcmd = ['hg', 'bisect']
2265 bisectcmd = ['hg', 'bisect']
2266 bisectrepo = self._runner.options.bisect_repo
2266 bisectrepo = self._runner.options.bisect_repo
2267 if bisectrepo:
2267 if bisectrepo:
2268 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2268 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2269 def pread(args):
2269 def pread(args):
2270 env = os.environ.copy()
2270 env = os.environ.copy()
2271 env['HGPLAIN'] = '1'
2271 env['HGPLAIN'] = '1'
2272 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2272 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2273 stdout=subprocess.PIPE, env=env)
2273 stdout=subprocess.PIPE, env=env)
2274 data = p.stdout.read()
2274 data = p.stdout.read()
2275 p.wait()
2275 p.wait()
2276 return data
2276 return data
2277 for test in tests:
2277 for test in tests:
2278 pread(bisectcmd + ['--reset']),
2278 pread(bisectcmd + ['--reset']),
2279 pread(bisectcmd + ['--bad', '.'])
2279 pread(bisectcmd + ['--bad', '.'])
2280 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2280 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2281 # TODO: we probably need to forward more options
2281 # TODO: we probably need to forward more options
2282 # that alter hg's behavior inside the tests.
2282 # that alter hg's behavior inside the tests.
2283 opts = ''
2283 opts = ''
2284 withhg = self._runner.options.with_hg
2284 withhg = self._runner.options.with_hg
2285 if withhg:
2285 if withhg:
2286 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
2286 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
2287 rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
2287 rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
2288 test)
2288 test)
2289 data = pread(bisectcmd + ['--command', rtc])
2289 data = pread(bisectcmd + ['--command', rtc])
2290 m = re.search(
2290 m = re.search(
2291 (br'\nThe first (?P<goodbad>bad|good) revision '
2291 (br'\nThe first (?P<goodbad>bad|good) revision '
2292 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2292 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2293 br'summary: +(?P<summary>[^\n]+)\n'),
2293 br'summary: +(?P<summary>[^\n]+)\n'),
2294 data, (re.MULTILINE | re.DOTALL))
2294 data, (re.MULTILINE | re.DOTALL))
2295 if m is None:
2295 if m is None:
2296 self.stream.writeln(
2296 self.stream.writeln(
2297 'Failed to identify failure point for %s' % test)
2297 'Failed to identify failure point for %s' % test)
2298 continue
2298 continue
2299 dat = m.groupdict()
2299 dat = m.groupdict()
2300 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2300 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2301 self.stream.writeln(
2301 self.stream.writeln(
2302 '%s %s by %s (%s)' % (
2302 '%s %s by %s (%s)' % (
2303 test, verb, dat['node'].decode('ascii'),
2303 test, verb, dat['node'].decode('ascii'),
2304 dat['summary'].decode('utf8', 'ignore')))
2304 dat['summary'].decode('utf8', 'ignore')))
2305
2305
2306 def printtimes(self, times):
2306 def printtimes(self, times):
2307 # iolock held by run
2307 # iolock held by run
2308 self.stream.writeln('# Producing time report')
2308 self.stream.writeln('# Producing time report')
2309 times.sort(key=lambda t: (t[3]))
2309 times.sort(key=lambda t: (t[3]))
2310 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2310 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2311 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2311 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2312 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2312 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2313 for tdata in times:
2313 for tdata in times:
2314 test = tdata[0]
2314 test = tdata[0]
2315 cuser, csys, real, start, end = tdata[1:6]
2315 cuser, csys, real, start, end = tdata[1:6]
2316 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2316 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2317
2317
2318 @staticmethod
2318 @staticmethod
2319 def _writexunit(result, outf):
2319 def _writexunit(result, outf):
2320 # See http://llg.cubic.org/docs/junit/ for a reference.
2320 # See http://llg.cubic.org/docs/junit/ for a reference.
2321 timesd = dict((t[0], t[3]) for t in result.times)
2321 timesd = dict((t[0], t[3]) for t in result.times)
2322 doc = minidom.Document()
2322 doc = minidom.Document()
2323 s = doc.createElement('testsuite')
2323 s = doc.createElement('testsuite')
2324 s.setAttribute('name', 'run-tests')
2324 s.setAttribute('name', 'run-tests')
2325 s.setAttribute('tests', str(result.testsRun))
2325 s.setAttribute('tests', str(result.testsRun))
2326 s.setAttribute('errors', "0") # TODO
2326 s.setAttribute('errors', "0") # TODO
2327 s.setAttribute('failures', str(len(result.failures)))
2327 s.setAttribute('failures', str(len(result.failures)))
2328 s.setAttribute('skipped', str(len(result.skipped) +
2328 s.setAttribute('skipped', str(len(result.skipped) +
2329 len(result.ignored)))
2329 len(result.ignored)))
2330 doc.appendChild(s)
2330 doc.appendChild(s)
2331 for tc in result.successes:
2331 for tc in result.successes:
2332 t = doc.createElement('testcase')
2332 t = doc.createElement('testcase')
2333 t.setAttribute('name', tc.name)
2333 t.setAttribute('name', tc.name)
2334 tctime = timesd.get(tc.name)
2334 tctime = timesd.get(tc.name)
2335 if tctime is not None:
2335 if tctime is not None:
2336 t.setAttribute('time', '%.3f' % tctime)
2336 t.setAttribute('time', '%.3f' % tctime)
2337 s.appendChild(t)
2337 s.appendChild(t)
2338 for tc, err in sorted(result.faildata.items()):
2338 for tc, err in sorted(result.faildata.items()):
2339 t = doc.createElement('testcase')
2339 t = doc.createElement('testcase')
2340 t.setAttribute('name', tc)
2340 t.setAttribute('name', tc)
2341 tctime = timesd.get(tc)
2341 tctime = timesd.get(tc)
2342 if tctime is not None:
2342 if tctime is not None:
2343 t.setAttribute('time', '%.3f' % tctime)
2343 t.setAttribute('time', '%.3f' % tctime)
2344 # createCDATASection expects a unicode or it will
2344 # createCDATASection expects a unicode or it will
2345 # convert using default conversion rules, which will
2345 # convert using default conversion rules, which will
2346 # fail if string isn't ASCII.
2346 # fail if string isn't ASCII.
2347 err = cdatasafe(err).decode('utf-8', 'replace')
2347 err = cdatasafe(err).decode('utf-8', 'replace')
2348 cd = doc.createCDATASection(err)
2348 cd = doc.createCDATASection(err)
2349 # Use 'failure' here instead of 'error' to match errors = 0,
2349 # Use 'failure' here instead of 'error' to match errors = 0,
2350 # failures = len(result.failures) in the testsuite element.
2350 # failures = len(result.failures) in the testsuite element.
2351 failelem = doc.createElement('failure')
2351 failelem = doc.createElement('failure')
2352 failelem.setAttribute('message', 'output changed')
2352 failelem.setAttribute('message', 'output changed')
2353 failelem.setAttribute('type', 'output-mismatch')
2353 failelem.setAttribute('type', 'output-mismatch')
2354 failelem.appendChild(cd)
2354 failelem.appendChild(cd)
2355 t.appendChild(failelem)
2355 t.appendChild(failelem)
2356 s.appendChild(t)
2356 s.appendChild(t)
2357 for tc, message in result.skipped:
2357 for tc, message in result.skipped:
2358 # According to the schema, 'skipped' has no attributes. So store
2358 # According to the schema, 'skipped' has no attributes. So store
2359 # the skip message as a text node instead.
2359 # the skip message as a text node instead.
2360 t = doc.createElement('testcase')
2360 t = doc.createElement('testcase')
2361 t.setAttribute('name', tc.name)
2361 t.setAttribute('name', tc.name)
2362 binmessage = message.encode('utf-8')
2362 binmessage = message.encode('utf-8')
2363 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2363 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2364 cd = doc.createCDATASection(message)
2364 cd = doc.createCDATASection(message)
2365 skipelem = doc.createElement('skipped')
2365 skipelem = doc.createElement('skipped')
2366 skipelem.appendChild(cd)
2366 skipelem.appendChild(cd)
2367 t.appendChild(skipelem)
2367 t.appendChild(skipelem)
2368 s.appendChild(t)
2368 s.appendChild(t)
2369 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2369 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2370
2370
2371 @staticmethod
2371 @staticmethod
2372 def _writejson(result, outf):
2372 def _writejson(result, outf):
2373 timesd = {}
2373 timesd = {}
2374 for tdata in result.times:
2374 for tdata in result.times:
2375 test = tdata[0]
2375 test = tdata[0]
2376 timesd[test] = tdata[1:]
2376 timesd[test] = tdata[1:]
2377
2377
2378 outcome = {}
2378 outcome = {}
2379 groups = [('success', ((tc, None)
2379 groups = [('success', ((tc, None)
2380 for tc in result.successes)),
2380 for tc in result.successes)),
2381 ('failure', result.failures),
2381 ('failure', result.failures),
2382 ('skip', result.skipped)]
2382 ('skip', result.skipped)]
2383 for res, testcases in groups:
2383 for res, testcases in groups:
2384 for tc, __ in testcases:
2384 for tc, __ in testcases:
2385 if tc.name in timesd:
2385 if tc.name in timesd:
2386 diff = result.faildata.get(tc.name, b'')
2386 diff = result.faildata.get(tc.name, b'')
2387 try:
2387 try:
2388 diff = diff.decode('unicode_escape')
2388 diff = diff.decode('unicode_escape')
2389 except UnicodeDecodeError as e:
2389 except UnicodeDecodeError as e:
2390 diff = '%r decoding diff, sorry' % e
2390 diff = '%r decoding diff, sorry' % e
2391 tres = {'result': res,
2391 tres = {'result': res,
2392 'time': ('%0.3f' % timesd[tc.name][2]),
2392 'time': ('%0.3f' % timesd[tc.name][2]),
2393 'cuser': ('%0.3f' % timesd[tc.name][0]),
2393 'cuser': ('%0.3f' % timesd[tc.name][0]),
2394 'csys': ('%0.3f' % timesd[tc.name][1]),
2394 'csys': ('%0.3f' % timesd[tc.name][1]),
2395 'start': ('%0.3f' % timesd[tc.name][3]),
2395 'start': ('%0.3f' % timesd[tc.name][3]),
2396 'end': ('%0.3f' % timesd[tc.name][4]),
2396 'end': ('%0.3f' % timesd[tc.name][4]),
2397 'diff': diff,
2397 'diff': diff,
2398 }
2398 }
2399 else:
2399 else:
2400 # blacklisted test
2400 # blacklisted test
2401 tres = {'result': res}
2401 tres = {'result': res}
2402
2402
2403 outcome[tc.name] = tres
2403 outcome[tc.name] = tres
2404 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2404 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2405 separators=(',', ': '))
2405 separators=(',', ': '))
2406 outf.writelines(("testreport =", jsonout))
2406 outf.writelines(("testreport =", jsonout))
2407
2407
2408 def sorttests(testdescs, previoustimes, shuffle=False):
2408 def sorttests(testdescs, previoustimes, shuffle=False):
2409 """Do an in-place sort of tests."""
2409 """Do an in-place sort of tests."""
2410 if shuffle:
2410 if shuffle:
2411 random.shuffle(testdescs)
2411 random.shuffle(testdescs)
2412 return
2412 return
2413
2413
2414 if previoustimes:
2414 if previoustimes:
2415 def sortkey(f):
2415 def sortkey(f):
2416 f = f['path']
2416 f = f['path']
2417 if f in previoustimes:
2417 if f in previoustimes:
2418 # Use most recent time as estimate
2418 # Use most recent time as estimate
2419 return -previoustimes[f][-1]
2419 return -previoustimes[f][-1]
2420 else:
2420 else:
2421 # Default to a rather arbitrary value of 1 second for new tests
2421 # Default to a rather arbitrary value of 1 second for new tests
2422 return -1.0
2422 return -1.0
2423 else:
2423 else:
2424 # keywords for slow tests
2424 # keywords for slow tests
2425 slow = {b'svn': 10,
2425 slow = {b'svn': 10,
2426 b'cvs': 10,
2426 b'cvs': 10,
2427 b'hghave': 10,
2427 b'hghave': 10,
2428 b'largefiles-update': 10,
2428 b'largefiles-update': 10,
2429 b'run-tests': 10,
2429 b'run-tests': 10,
2430 b'corruption': 10,
2430 b'corruption': 10,
2431 b'race': 10,
2431 b'race': 10,
2432 b'i18n': 10,
2432 b'i18n': 10,
2433 b'check': 100,
2433 b'check': 100,
2434 b'gendoc': 100,
2434 b'gendoc': 100,
2435 b'contrib-perf': 200,
2435 b'contrib-perf': 200,
2436 }
2436 }
2437 perf = {}
2437 perf = {}
2438
2438
2439 def sortkey(f):
2439 def sortkey(f):
2440 # run largest tests first, as they tend to take the longest
2440 # run largest tests first, as they tend to take the longest
2441 f = f['path']
2441 f = f['path']
2442 try:
2442 try:
2443 return perf[f]
2443 return perf[f]
2444 except KeyError:
2444 except KeyError:
2445 try:
2445 try:
2446 val = -os.stat(f).st_size
2446 val = -os.stat(f).st_size
2447 except OSError as e:
2447 except OSError as e:
2448 if e.errno != errno.ENOENT:
2448 if e.errno != errno.ENOENT:
2449 raise
2449 raise
2450 perf[f] = -1e9 # file does not exist, tell early
2450 perf[f] = -1e9 # file does not exist, tell early
2451 return -1e9
2451 return -1e9
2452 for kw, mul in slow.items():
2452 for kw, mul in slow.items():
2453 if kw in f:
2453 if kw in f:
2454 val *= mul
2454 val *= mul
2455 if f.endswith(b'.py'):
2455 if f.endswith(b'.py'):
2456 val /= 10.0
2456 val /= 10.0
2457 perf[f] = val / 1000.0
2457 perf[f] = val / 1000.0
2458 return perf[f]
2458 return perf[f]
2459
2459
2460 testdescs.sort(key=sortkey)
2460 testdescs.sort(key=sortkey)
2461
2461
2462 class TestRunner(object):
2462 class TestRunner(object):
2463 """Holds context for executing tests.
2463 """Holds context for executing tests.
2464
2464
2465 Tests rely on a lot of state. This object holds it for them.
2465 Tests rely on a lot of state. This object holds it for them.
2466 """
2466 """
2467
2467
2468 # Programs required to run tests.
2468 # Programs required to run tests.
2469 REQUIREDTOOLS = [
2469 REQUIREDTOOLS = [
2470 b'diff',
2470 b'diff',
2471 b'grep',
2471 b'grep',
2472 b'unzip',
2472 b'unzip',
2473 b'gunzip',
2473 b'gunzip',
2474 b'bunzip2',
2474 b'bunzip2',
2475 b'sed',
2475 b'sed',
2476 ]
2476 ]
2477
2477
2478 # Maps file extensions to test class.
2478 # Maps file extensions to test class.
2479 TESTTYPES = [
2479 TESTTYPES = [
2480 (b'.py', PythonTest),
2480 (b'.py', PythonTest),
2481 (b'.t', TTest),
2481 (b'.t', TTest),
2482 ]
2482 ]
2483
2483
2484 def __init__(self):
2484 def __init__(self):
2485 self.options = None
2485 self.options = None
2486 self._hgroot = None
2486 self._hgroot = None
2487 self._testdir = None
2487 self._testdir = None
2488 self._outputdir = None
2488 self._outputdir = None
2489 self._hgtmp = None
2489 self._hgtmp = None
2490 self._installdir = None
2490 self._installdir = None
2491 self._bindir = None
2491 self._bindir = None
2492 self._tmpbinddir = None
2492 self._tmpbinddir = None
2493 self._pythondir = None
2493 self._pythondir = None
2494 self._coveragefile = None
2494 self._coveragefile = None
2495 self._createdfiles = []
2495 self._createdfiles = []
2496 self._hgcommand = None
2496 self._hgcommand = None
2497 self._hgpath = None
2497 self._hgpath = None
2498 self._portoffset = 0
2498 self._portoffset = 0
2499 self._ports = {}
2499 self._ports = {}
2500
2500
2501 def run(self, args, parser=None):
2501 def run(self, args, parser=None):
2502 """Run the test suite."""
2502 """Run the test suite."""
2503 oldmask = os.umask(0o22)
2503 oldmask = os.umask(0o22)
2504 try:
2504 try:
2505 parser = parser or getparser()
2505 parser = parser or getparser()
2506 options = parseargs(args, parser)
2506 options = parseargs(args, parser)
2507 tests = [_bytespath(a) for a in options.tests]
2507 tests = [_bytespath(a) for a in options.tests]
2508 if options.test_list is not None:
2508 if options.test_list is not None:
2509 for listfile in options.test_list:
2509 for listfile in options.test_list:
2510 with open(listfile, 'rb') as f:
2510 with open(listfile, 'rb') as f:
2511 tests.extend(t for t in f.read().splitlines() if t)
2511 tests.extend(t for t in f.read().splitlines() if t)
2512 self.options = options
2512 self.options = options
2513
2513
2514 self._checktools()
2514 self._checktools()
2515 testdescs = self.findtests(tests)
2515 testdescs = self.findtests(tests)
2516 if options.profile_runner:
2516 if options.profile_runner:
2517 import statprof
2517 import statprof
2518 statprof.start()
2518 statprof.start()
2519 result = self._run(testdescs)
2519 result = self._run(testdescs)
2520 if options.profile_runner:
2520 if options.profile_runner:
2521 statprof.stop()
2521 statprof.stop()
2522 statprof.display()
2522 statprof.display()
2523 return result
2523 return result
2524
2524
2525 finally:
2525 finally:
2526 os.umask(oldmask)
2526 os.umask(oldmask)
2527
2527
2528 def _run(self, testdescs):
2528 def _run(self, testdescs):
2529 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2529 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2530 # assume all tests in same folder for now
2530 # assume all tests in same folder for now
2531 if testdescs:
2531 if testdescs:
2532 pathname = os.path.dirname(testdescs[0]['path'])
2532 pathname = os.path.dirname(testdescs[0]['path'])
2533 if pathname:
2533 if pathname:
2534 osenvironb[b'TESTDIR'] = os.path.join(osenvironb[b'TESTDIR'],
2534 osenvironb[b'TESTDIR'] = os.path.join(osenvironb[b'TESTDIR'],
2535 pathname)
2535 pathname)
2536 if self.options.outputdir:
2536 if self.options.outputdir:
2537 self._outputdir = canonpath(_bytespath(self.options.outputdir))
2537 self._outputdir = canonpath(_bytespath(self.options.outputdir))
2538 else:
2538 else:
2539 self._outputdir = self._testdir
2539 self._outputdir = self._testdir
2540 if testdescs and pathname:
2540 if testdescs and pathname:
2541 self._outputdir = os.path.join(self._outputdir, pathname)
2541 self._outputdir = os.path.join(self._outputdir, pathname)
2542 previoustimes = {}
2542 previoustimes = {}
2543 if self.options.order_by_runtime:
2543 if self.options.order_by_runtime:
2544 previoustimes = dict(loadtimes(self._outputdir))
2544 previoustimes = dict(loadtimes(self._outputdir))
2545 sorttests(testdescs, previoustimes, shuffle=self.options.random)
2545 sorttests(testdescs, previoustimes, shuffle=self.options.random)
2546
2546
2547 if 'PYTHONHASHSEED' not in os.environ:
2547 if 'PYTHONHASHSEED' not in os.environ:
2548 # use a random python hash seed all the time
2548 # use a random python hash seed all the time
2549 # we do the randomness ourself to know what seed is used
2549 # we do the randomness ourself to know what seed is used
2550 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2550 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2551
2551
2552 if self.options.tmpdir:
2552 if self.options.tmpdir:
2553 self.options.keep_tmpdir = True
2553 self.options.keep_tmpdir = True
2554 tmpdir = _bytespath(self.options.tmpdir)
2554 tmpdir = _bytespath(self.options.tmpdir)
2555 if os.path.exists(tmpdir):
2555 if os.path.exists(tmpdir):
2556 # Meaning of tmpdir has changed since 1.3: we used to create
2556 # Meaning of tmpdir has changed since 1.3: we used to create
2557 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2557 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2558 # tmpdir already exists.
2558 # tmpdir already exists.
2559 print("error: temp dir %r already exists" % tmpdir)
2559 print("error: temp dir %r already exists" % tmpdir)
2560 return 1
2560 return 1
2561
2561
2562 os.makedirs(tmpdir)
2562 os.makedirs(tmpdir)
2563 else:
2563 else:
2564 d = None
2564 d = None
2565 if os.name == 'nt':
2565 if os.name == 'nt':
2566 # without this, we get the default temp dir location, but
2566 # without this, we get the default temp dir location, but
2567 # in all lowercase, which causes troubles with paths (issue3490)
2567 # in all lowercase, which causes troubles with paths (issue3490)
2568 d = osenvironb.get(b'TMP', None)
2568 d = osenvironb.get(b'TMP', None)
2569 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2569 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2570
2570
2571 self._hgtmp = osenvironb[b'HGTMP'] = (
2571 self._hgtmp = osenvironb[b'HGTMP'] = (
2572 os.path.realpath(tmpdir))
2572 os.path.realpath(tmpdir))
2573
2573
2574 if self.options.with_hg:
2574 if self.options.with_hg:
2575 self._installdir = None
2575 self._installdir = None
2576 whg = self.options.with_hg
2576 whg = self.options.with_hg
2577 self._bindir = os.path.dirname(os.path.realpath(whg))
2577 self._bindir = os.path.dirname(os.path.realpath(whg))
2578 assert isinstance(self._bindir, bytes)
2578 assert isinstance(self._bindir, bytes)
2579 self._hgcommand = os.path.basename(whg)
2579 self._hgcommand = os.path.basename(whg)
2580 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2580 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2581 os.makedirs(self._tmpbindir)
2581 os.makedirs(self._tmpbindir)
2582
2582
2583 normbin = os.path.normpath(os.path.abspath(whg))
2583 normbin = os.path.normpath(os.path.abspath(whg))
2584 normbin = normbin.replace(os.sep.encode('ascii'), b'/')
2584 normbin = normbin.replace(os.sep.encode('ascii'), b'/')
2585
2585
2586 # Other Python scripts in the test harness need to
2586 # Other Python scripts in the test harness need to
2587 # `import mercurial`. If `hg` is a Python script, we assume
2587 # `import mercurial`. If `hg` is a Python script, we assume
2588 # the Mercurial modules are relative to its path and tell the tests
2588 # the Mercurial modules are relative to its path and tell the tests
2589 # to load Python modules from its directory.
2589 # to load Python modules from its directory.
2590 with open(whg, 'rb') as fh:
2590 with open(whg, 'rb') as fh:
2591 initial = fh.read(1024)
2591 initial = fh.read(1024)
2592
2592
2593 if re.match(b'#!.*python', initial):
2593 if re.match(b'#!.*python', initial):
2594 self._pythondir = self._bindir
2594 self._pythondir = self._bindir
2595 # If it looks like our in-repo Rust binary, use the source root.
2595 # If it looks like our in-repo Rust binary, use the source root.
2596 # This is a bit hacky. But rhg is still not supported outside the
2596 # This is a bit hacky. But rhg is still not supported outside the
2597 # source directory. So until it is, do the simple thing.
2597 # source directory. So until it is, do the simple thing.
2598 elif re.search(b'/rust/target/[^/]+/hg', normbin):
2598 elif re.search(b'/rust/target/[^/]+/hg', normbin):
2599 self._pythondir = os.path.dirname(self._testdir)
2599 self._pythondir = os.path.dirname(self._testdir)
2600 # Fall back to the legacy behavior.
2600 # Fall back to the legacy behavior.
2601 else:
2601 else:
2602 self._pythondir = self._bindir
2602 self._pythondir = self._bindir
2603
2603
2604 else:
2604 else:
2605 self._installdir = os.path.join(self._hgtmp, b"install")
2605 self._installdir = os.path.join(self._hgtmp, b"install")
2606 self._bindir = os.path.join(self._installdir, b"bin")
2606 self._bindir = os.path.join(self._installdir, b"bin")
2607 self._hgcommand = b'hg'
2607 self._hgcommand = b'hg'
2608 self._tmpbindir = self._bindir
2608 self._tmpbindir = self._bindir
2609 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2609 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2610
2610
2611 # set CHGHG, then replace "hg" command by "chg"
2611 # set CHGHG, then replace "hg" command by "chg"
2612 chgbindir = self._bindir
2612 chgbindir = self._bindir
2613 if self.options.chg or self.options.with_chg:
2613 if self.options.chg or self.options.with_chg:
2614 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2614 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2615 else:
2615 else:
2616 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2616 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2617 if self.options.chg:
2617 if self.options.chg:
2618 self._hgcommand = b'chg'
2618 self._hgcommand = b'chg'
2619 elif self.options.with_chg:
2619 elif self.options.with_chg:
2620 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2620 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2621 self._hgcommand = os.path.basename(self.options.with_chg)
2621 self._hgcommand = os.path.basename(self.options.with_chg)
2622
2622
2623 osenvironb[b"BINDIR"] = self._bindir
2623 osenvironb[b"BINDIR"] = self._bindir
2624 osenvironb[b"PYTHON"] = PYTHON
2624 osenvironb[b"PYTHON"] = PYTHON
2625
2625
2626 fileb = _bytespath(__file__)
2626 fileb = _bytespath(__file__)
2627 runtestdir = os.path.abspath(os.path.dirname(fileb))
2627 runtestdir = os.path.abspath(os.path.dirname(fileb))
2628 osenvironb[b'RUNTESTDIR'] = runtestdir
2628 osenvironb[b'RUNTESTDIR'] = runtestdir
2629 if PYTHON3:
2629 if PYTHON3:
2630 sepb = _bytespath(os.pathsep)
2630 sepb = _bytespath(os.pathsep)
2631 else:
2631 else:
2632 sepb = os.pathsep
2632 sepb = os.pathsep
2633 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2633 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2634 if os.path.islink(__file__):
2634 if os.path.islink(__file__):
2635 # test helper will likely be at the end of the symlink
2635 # test helper will likely be at the end of the symlink
2636 realfile = os.path.realpath(fileb)
2636 realfile = os.path.realpath(fileb)
2637 realdir = os.path.abspath(os.path.dirname(realfile))
2637 realdir = os.path.abspath(os.path.dirname(realfile))
2638 path.insert(2, realdir)
2638 path.insert(2, realdir)
2639 if chgbindir != self._bindir:
2639 if chgbindir != self._bindir:
2640 path.insert(1, chgbindir)
2640 path.insert(1, chgbindir)
2641 if self._testdir != runtestdir:
2641 if self._testdir != runtestdir:
2642 path = [self._testdir] + path
2642 path = [self._testdir] + path
2643 if self._tmpbindir != self._bindir:
2643 if self._tmpbindir != self._bindir:
2644 path = [self._tmpbindir] + path
2644 path = [self._tmpbindir] + path
2645 osenvironb[b"PATH"] = sepb.join(path)
2645 osenvironb[b"PATH"] = sepb.join(path)
2646
2646
2647 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2647 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2648 # can run .../tests/run-tests.py test-foo where test-foo
2648 # can run .../tests/run-tests.py test-foo where test-foo
2649 # adds an extension to HGRC. Also include run-test.py directory to
2649 # adds an extension to HGRC. Also include run-test.py directory to
2650 # import modules like heredoctest.
2650 # import modules like heredoctest.
2651 pypath = [self._pythondir, self._testdir, runtestdir]
2651 pypath = [self._pythondir, self._testdir, runtestdir]
2652 # We have to augment PYTHONPATH, rather than simply replacing
2652 # We have to augment PYTHONPATH, rather than simply replacing
2653 # it, in case external libraries are only available via current
2653 # it, in case external libraries are only available via current
2654 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2654 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2655 # are in /opt/subversion.)
2655 # are in /opt/subversion.)
2656 oldpypath = osenvironb.get(IMPL_PATH)
2656 oldpypath = osenvironb.get(IMPL_PATH)
2657 if oldpypath:
2657 if oldpypath:
2658 pypath.append(oldpypath)
2658 pypath.append(oldpypath)
2659 osenvironb[IMPL_PATH] = sepb.join(pypath)
2659 osenvironb[IMPL_PATH] = sepb.join(pypath)
2660
2660
2661 if self.options.pure:
2661 if self.options.pure:
2662 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2662 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2663 os.environ["HGMODULEPOLICY"] = "py"
2663 os.environ["HGMODULEPOLICY"] = "py"
2664
2664
2665 if self.options.allow_slow_tests:
2665 if self.options.allow_slow_tests:
2666 os.environ["HGTEST_SLOW"] = "slow"
2666 os.environ["HGTEST_SLOW"] = "slow"
2667 elif 'HGTEST_SLOW' in os.environ:
2667 elif 'HGTEST_SLOW' in os.environ:
2668 del os.environ['HGTEST_SLOW']
2668 del os.environ['HGTEST_SLOW']
2669
2669
2670 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2670 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2671
2671
2672 if self.options.exceptions:
2672 if self.options.exceptions:
2673 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
2673 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
2674 try:
2674 try:
2675 os.makedirs(exceptionsdir)
2675 os.makedirs(exceptionsdir)
2676 except OSError as e:
2676 except OSError as e:
2677 if e.errno != errno.EEXIST:
2677 if e.errno != errno.EEXIST:
2678 raise
2678 raise
2679
2679
2680 # Remove all existing exception reports.
2680 # Remove all existing exception reports.
2681 for f in os.listdir(exceptionsdir):
2681 for f in os.listdir(exceptionsdir):
2682 os.unlink(os.path.join(exceptionsdir, f))
2682 os.unlink(os.path.join(exceptionsdir, f))
2683
2683
2684 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
2684 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
2685 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
2685 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
2686 self.options.extra_config_opt.append(
2686 self.options.extra_config_opt.append(
2687 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
2687 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
2688
2688
2689 vlog("# Using TESTDIR", self._testdir)
2689 vlog("# Using TESTDIR", self._testdir)
2690 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2690 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2691 vlog("# Using HGTMP", self._hgtmp)
2691 vlog("# Using HGTMP", self._hgtmp)
2692 vlog("# Using PATH", os.environ["PATH"])
2692 vlog("# Using PATH", os.environ["PATH"])
2693 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2693 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2694 vlog("# Writing to directory", self._outputdir)
2694 vlog("# Writing to directory", self._outputdir)
2695
2695
2696 try:
2696 try:
2697 return self._runtests(testdescs) or 0
2697 return self._runtests(testdescs) or 0
2698 finally:
2698 finally:
2699 time.sleep(.1)
2699 time.sleep(.1)
2700 self._cleanup()
2700 self._cleanup()
2701
2701
2702 def findtests(self, args):
2702 def findtests(self, args):
2703 """Finds possible test files from arguments.
2703 """Finds possible test files from arguments.
2704
2704
2705 If you wish to inject custom tests into the test harness, this would
2705 If you wish to inject custom tests into the test harness, this would
2706 be a good function to monkeypatch or override in a derived class.
2706 be a good function to monkeypatch or override in a derived class.
2707 """
2707 """
2708 if not args:
2708 if not args:
2709 if self.options.changed:
2709 if self.options.changed:
2710 proc = Popen4('hg st --rev "%s" -man0 .' %
2710 proc = Popen4('hg st --rev "%s" -man0 .' %
2711 self.options.changed, None, 0)
2711 self.options.changed, None, 0)
2712 stdout, stderr = proc.communicate()
2712 stdout, stderr = proc.communicate()
2713 args = stdout.strip(b'\0').split(b'\0')
2713 args = stdout.strip(b'\0').split(b'\0')
2714 else:
2714 else:
2715 args = os.listdir(b'.')
2715 args = os.listdir(b'.')
2716
2716
2717 expanded_args = []
2717 expanded_args = []
2718 for arg in args:
2718 for arg in args:
2719 if os.path.isdir(arg):
2719 if os.path.isdir(arg):
2720 if not arg.endswith(b'/'):
2720 if not arg.endswith(b'/'):
2721 arg += b'/'
2721 arg += b'/'
2722 expanded_args.extend([arg + a for a in os.listdir(arg)])
2722 expanded_args.extend([arg + a for a in os.listdir(arg)])
2723 else:
2723 else:
2724 expanded_args.append(arg)
2724 expanded_args.append(arg)
2725 args = expanded_args
2725 args = expanded_args
2726
2726
2727 testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.#]+))')
2727 testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.#]+))')
2728 tests = []
2728 tests = []
2729 for t in args:
2729 for t in args:
2730 case = []
2730 case = []
2731
2731
2732 if not (os.path.basename(t).startswith(b'test-')
2732 if not (os.path.basename(t).startswith(b'test-')
2733 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2733 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2734
2734
2735 m = testcasepattern.match(t)
2735 m = testcasepattern.match(t)
2736 if m is not None:
2736 if m is not None:
2737 t, _, casestr = m.groups()
2737 t, _, casestr = m.groups()
2738 if casestr:
2738 if casestr:
2739 case = casestr.split(b'#')
2739 case = casestr.split(b'#')
2740 else:
2740 else:
2741 continue
2741 continue
2742
2742
2743 if t.endswith(b'.t'):
2743 if t.endswith(b'.t'):
2744 # .t file may contain multiple test cases
2744 # .t file may contain multiple test cases
2745 casedimensions = parsettestcases(t)
2745 casedimensions = parsettestcases(t)
2746 if casedimensions:
2746 if casedimensions:
2747 cases = []
2747 cases = []
2748 def addcases(case, casedimensions):
2748 def addcases(case, casedimensions):
2749 if not casedimensions:
2749 if not casedimensions:
2750 cases.append(case)
2750 cases.append(case)
2751 else:
2751 else:
2752 for c in casedimensions[0]:
2752 for c in casedimensions[0]:
2753 addcases(case + [c], casedimensions[1:])
2753 addcases(case + [c], casedimensions[1:])
2754 addcases([], casedimensions)
2754 addcases([], casedimensions)
2755 if case and case in cases:
2755 if case and case in cases:
2756 cases = [case]
2756 cases = [case]
2757 elif case:
2757 elif case:
2758 # Ignore invalid cases
2758 # Ignore invalid cases
2759 cases = []
2759 cases = []
2760 else:
2760 else:
2761 pass
2761 pass
2762 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2762 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2763 else:
2763 else:
2764 tests.append({'path': t})
2764 tests.append({'path': t})
2765 else:
2765 else:
2766 tests.append({'path': t})
2766 tests.append({'path': t})
2767 return tests
2767 return tests
2768
2768
2769 def _runtests(self, testdescs):
2769 def _runtests(self, testdescs):
2770 def _reloadtest(test, i):
2770 def _reloadtest(test, i):
2771 # convert a test back to its description dict
2771 # convert a test back to its description dict
2772 desc = {'path': test.path}
2772 desc = {'path': test.path}
2773 case = getattr(test, '_case', [])
2773 case = getattr(test, '_case', [])
2774 if case:
2774 if case:
2775 desc['case'] = case
2775 desc['case'] = case
2776 return self._gettest(desc, i)
2776 return self._gettest(desc, i)
2777
2777
2778 try:
2778 try:
2779 if self.options.restart:
2779 if self.options.restart:
2780 orig = list(testdescs)
2780 orig = list(testdescs)
2781 while testdescs:
2781 while testdescs:
2782 desc = testdescs[0]
2782 desc = testdescs[0]
2783 # desc['path'] is a relative path
2783 # desc['path'] is a relative path
2784 if 'case' in desc:
2784 if 'case' in desc:
2785 casestr = b'#'.join(desc['case'])
2785 casestr = b'#'.join(desc['case'])
2786 errpath = b'%s#%s.err' % (desc['path'], casestr)
2786 errpath = b'%s#%s.err' % (desc['path'], casestr)
2787 else:
2787 else:
2788 errpath = b'%s.err' % desc['path']
2788 errpath = b'%s.err' % desc['path']
2789 errpath = os.path.join(self._outputdir, errpath)
2789 errpath = os.path.join(self._outputdir, errpath)
2790 if os.path.exists(errpath):
2790 if os.path.exists(errpath):
2791 break
2791 break
2792 testdescs.pop(0)
2792 testdescs.pop(0)
2793 if not testdescs:
2793 if not testdescs:
2794 print("running all tests")
2794 print("running all tests")
2795 testdescs = orig
2795 testdescs = orig
2796
2796
2797 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2797 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2798
2798
2799 jobs = min(len(tests), self.options.jobs)
2799 jobs = min(len(tests), self.options.jobs)
2800
2800
2801 failed = False
2801 failed = False
2802 kws = self.options.keywords
2802 kws = self.options.keywords
2803 if kws is not None and PYTHON3:
2803 if kws is not None and PYTHON3:
2804 kws = kws.encode('utf-8')
2804 kws = kws.encode('utf-8')
2805
2805
2806 suite = TestSuite(self._testdir,
2806 suite = TestSuite(self._testdir,
2807 jobs=jobs,
2807 jobs=jobs,
2808 whitelist=self.options.whitelisted,
2808 whitelist=self.options.whitelisted,
2809 blacklist=self.options.blacklist,
2809 blacklist=self.options.blacklist,
2810 retest=self.options.retest,
2810 retest=self.options.retest,
2811 keywords=kws,
2811 keywords=kws,
2812 loop=self.options.loop,
2812 loop=self.options.loop,
2813 runs_per_test=self.options.runs_per_test,
2813 runs_per_test=self.options.runs_per_test,
2814 showchannels=self.options.showchannels,
2814 showchannels=self.options.showchannels,
2815 tests=tests, loadtest=_reloadtest)
2815 tests=tests, loadtest=_reloadtest)
2816 verbosity = 1
2816 verbosity = 1
2817 if self.options.list_tests:
2817 if self.options.list_tests:
2818 verbosity = 0
2818 verbosity = 0
2819 elif self.options.verbose:
2819 elif self.options.verbose:
2820 verbosity = 2
2820 verbosity = 2
2821 runner = TextTestRunner(self, verbosity=verbosity)
2821 runner = TextTestRunner(self, verbosity=verbosity)
2822
2822
2823 if self.options.list_tests:
2823 if self.options.list_tests:
2824 result = runner.listtests(suite)
2824 result = runner.listtests(suite)
2825 else:
2825 else:
2826 if self._installdir:
2826 if self._installdir:
2827 self._installhg()
2827 self._installhg()
2828 self._checkhglib("Testing")
2828 self._checkhglib("Testing")
2829 else:
2829 else:
2830 self._usecorrectpython()
2830 self._usecorrectpython()
2831 if self.options.chg:
2831 if self.options.chg:
2832 assert self._installdir
2832 assert self._installdir
2833 self._installchg()
2833 self._installchg()
2834
2834
2835 log('running %d tests using %d parallel processes' % (
2835 log('running %d tests using %d parallel processes' % (
2836 len(tests), jobs))
2836 len(tests), jobs))
2837
2837
2838 result = runner.run(suite)
2838 result = runner.run(suite)
2839
2839
2840 if result.failures:
2840 if result.failures:
2841 failed = True
2841 failed = True
2842
2842
2843 result.onEnd()
2843 result.onEnd()
2844
2844
2845 if self.options.anycoverage:
2845 if self.options.anycoverage:
2846 self._outputcoverage()
2846 self._outputcoverage()
2847 except KeyboardInterrupt:
2847 except KeyboardInterrupt:
2848 failed = True
2848 failed = True
2849 print("\ninterrupted!")
2849 print("\ninterrupted!")
2850
2850
2851 if failed:
2851 if failed:
2852 return 1
2852 return 1
2853
2853
2854 def _getport(self, count):
2854 def _getport(self, count):
2855 port = self._ports.get(count) # do we have a cached entry?
2855 port = self._ports.get(count) # do we have a cached entry?
2856 if port is None:
2856 if port is None:
2857 portneeded = 3
2857 portneeded = 3
2858 # above 100 tries we just give up and let test reports failure
2858 # above 100 tries we just give up and let test reports failure
2859 for tries in xrange(100):
2859 for tries in xrange(100):
2860 allfree = True
2860 allfree = True
2861 port = self.options.port + self._portoffset
2861 port = self.options.port + self._portoffset
2862 for idx in xrange(portneeded):
2862 for idx in xrange(portneeded):
2863 if not checkportisavailable(port + idx):
2863 if not checkportisavailable(port + idx):
2864 allfree = False
2864 allfree = False
2865 break
2865 break
2866 self._portoffset += portneeded
2866 self._portoffset += portneeded
2867 if allfree:
2867 if allfree:
2868 break
2868 break
2869 self._ports[count] = port
2869 self._ports[count] = port
2870 return port
2870 return port
2871
2871
2872 def _gettest(self, testdesc, count):
2872 def _gettest(self, testdesc, count):
2873 """Obtain a Test by looking at its filename.
2873 """Obtain a Test by looking at its filename.
2874
2874
2875 Returns a Test instance. The Test may not be runnable if it doesn't
2875 Returns a Test instance. The Test may not be runnable if it doesn't
2876 map to a known type.
2876 map to a known type.
2877 """
2877 """
2878 path = testdesc['path']
2878 path = testdesc['path']
2879 lctest = path.lower()
2879 lctest = path.lower()
2880 testcls = Test
2880 testcls = Test
2881
2881
2882 for ext, cls in self.TESTTYPES:
2882 for ext, cls in self.TESTTYPES:
2883 if lctest.endswith(ext):
2883 if lctest.endswith(ext):
2884 testcls = cls
2884 testcls = cls
2885 break
2885 break
2886
2886
2887 refpath = os.path.join(self._testdir, path)
2887 refpath = os.path.join(self._testdir, path)
2888 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2888 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2889
2889
2890 # extra keyword parameters. 'case' is used by .t tests
2890 # extra keyword parameters. 'case' is used by .t tests
2891 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2891 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2892
2892
2893 t = testcls(refpath, self._outputdir, tmpdir,
2893 t = testcls(refpath, self._outputdir, tmpdir,
2894 keeptmpdir=self.options.keep_tmpdir,
2894 keeptmpdir=self.options.keep_tmpdir,
2895 debug=self.options.debug,
2895 debug=self.options.debug,
2896 first=self.options.first,
2896 first=self.options.first,
2897 timeout=self.options.timeout,
2897 timeout=self.options.timeout,
2898 startport=self._getport(count),
2898 startport=self._getport(count),
2899 extraconfigopts=self.options.extra_config_opt,
2899 extraconfigopts=self.options.extra_config_opt,
2900 py3kwarnings=self.options.py3k_warnings,
2900 py3warnings=self.options.py3_warnings,
2901 shell=self.options.shell,
2901 shell=self.options.shell,
2902 hgcommand=self._hgcommand,
2902 hgcommand=self._hgcommand,
2903 usechg=bool(self.options.with_chg or self.options.chg),
2903 usechg=bool(self.options.with_chg or self.options.chg),
2904 useipv6=useipv6, **kwds)
2904 useipv6=useipv6, **kwds)
2905 t.should_reload = True
2905 t.should_reload = True
2906 return t
2906 return t
2907
2907
2908 def _cleanup(self):
2908 def _cleanup(self):
2909 """Clean up state from this test invocation."""
2909 """Clean up state from this test invocation."""
2910 if self.options.keep_tmpdir:
2910 if self.options.keep_tmpdir:
2911 return
2911 return
2912
2912
2913 vlog("# Cleaning up HGTMP", self._hgtmp)
2913 vlog("# Cleaning up HGTMP", self._hgtmp)
2914 shutil.rmtree(self._hgtmp, True)
2914 shutil.rmtree(self._hgtmp, True)
2915 for f in self._createdfiles:
2915 for f in self._createdfiles:
2916 try:
2916 try:
2917 os.remove(f)
2917 os.remove(f)
2918 except OSError:
2918 except OSError:
2919 pass
2919 pass
2920
2920
2921 def _usecorrectpython(self):
2921 def _usecorrectpython(self):
2922 """Configure the environment to use the appropriate Python in tests."""
2922 """Configure the environment to use the appropriate Python in tests."""
2923 # Tests must use the same interpreter as us or bad things will happen.
2923 # Tests must use the same interpreter as us or bad things will happen.
2924 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2924 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2925
2925
2926 # os.symlink() is a thing with py3 on Windows, but it requires
2926 # os.symlink() is a thing with py3 on Windows, but it requires
2927 # Administrator rights.
2927 # Administrator rights.
2928 if getattr(os, 'symlink', None) and os.name != 'nt':
2928 if getattr(os, 'symlink', None) and os.name != 'nt':
2929 vlog("# Making python executable in test path a symlink to '%s'" %
2929 vlog("# Making python executable in test path a symlink to '%s'" %
2930 sys.executable)
2930 sys.executable)
2931 mypython = os.path.join(self._tmpbindir, pyexename)
2931 mypython = os.path.join(self._tmpbindir, pyexename)
2932 try:
2932 try:
2933 if os.readlink(mypython) == sys.executable:
2933 if os.readlink(mypython) == sys.executable:
2934 return
2934 return
2935 os.unlink(mypython)
2935 os.unlink(mypython)
2936 except OSError as err:
2936 except OSError as err:
2937 if err.errno != errno.ENOENT:
2937 if err.errno != errno.ENOENT:
2938 raise
2938 raise
2939 if self._findprogram(pyexename) != sys.executable:
2939 if self._findprogram(pyexename) != sys.executable:
2940 try:
2940 try:
2941 os.symlink(sys.executable, mypython)
2941 os.symlink(sys.executable, mypython)
2942 self._createdfiles.append(mypython)
2942 self._createdfiles.append(mypython)
2943 except OSError as err:
2943 except OSError as err:
2944 # child processes may race, which is harmless
2944 # child processes may race, which is harmless
2945 if err.errno != errno.EEXIST:
2945 if err.errno != errno.EEXIST:
2946 raise
2946 raise
2947 else:
2947 else:
2948 exedir, exename = os.path.split(sys.executable)
2948 exedir, exename = os.path.split(sys.executable)
2949 vlog("# Modifying search path to find %s as %s in '%s'" %
2949 vlog("# Modifying search path to find %s as %s in '%s'" %
2950 (exename, pyexename, exedir))
2950 (exename, pyexename, exedir))
2951 path = os.environ['PATH'].split(os.pathsep)
2951 path = os.environ['PATH'].split(os.pathsep)
2952 while exedir in path:
2952 while exedir in path:
2953 path.remove(exedir)
2953 path.remove(exedir)
2954 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2954 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2955 if not self._findprogram(pyexename):
2955 if not self._findprogram(pyexename):
2956 print("WARNING: Cannot find %s in search path" % pyexename)
2956 print("WARNING: Cannot find %s in search path" % pyexename)
2957
2957
2958 def _installhg(self):
2958 def _installhg(self):
2959 """Install hg into the test environment.
2959 """Install hg into the test environment.
2960
2960
2961 This will also configure hg with the appropriate testing settings.
2961 This will also configure hg with the appropriate testing settings.
2962 """
2962 """
2963 vlog("# Performing temporary installation of HG")
2963 vlog("# Performing temporary installation of HG")
2964 installerrs = os.path.join(self._hgtmp, b"install.err")
2964 installerrs = os.path.join(self._hgtmp, b"install.err")
2965 compiler = ''
2965 compiler = ''
2966 if self.options.compiler:
2966 if self.options.compiler:
2967 compiler = '--compiler ' + self.options.compiler
2967 compiler = '--compiler ' + self.options.compiler
2968 if self.options.pure:
2968 if self.options.pure:
2969 pure = b"--pure"
2969 pure = b"--pure"
2970 else:
2970 else:
2971 pure = b""
2971 pure = b""
2972
2972
2973 # Run installer in hg root
2973 # Run installer in hg root
2974 script = os.path.realpath(sys.argv[0])
2974 script = os.path.realpath(sys.argv[0])
2975 exe = sys.executable
2975 exe = sys.executable
2976 if PYTHON3:
2976 if PYTHON3:
2977 compiler = _bytespath(compiler)
2977 compiler = _bytespath(compiler)
2978 script = _bytespath(script)
2978 script = _bytespath(script)
2979 exe = _bytespath(exe)
2979 exe = _bytespath(exe)
2980 hgroot = os.path.dirname(os.path.dirname(script))
2980 hgroot = os.path.dirname(os.path.dirname(script))
2981 self._hgroot = hgroot
2981 self._hgroot = hgroot
2982 os.chdir(hgroot)
2982 os.chdir(hgroot)
2983 nohome = b'--home=""'
2983 nohome = b'--home=""'
2984 if os.name == 'nt':
2984 if os.name == 'nt':
2985 # The --home="" trick works only on OS where os.sep == '/'
2985 # The --home="" trick works only on OS where os.sep == '/'
2986 # because of a distutils convert_path() fast-path. Avoid it at
2986 # because of a distutils convert_path() fast-path. Avoid it at
2987 # least on Windows for now, deal with .pydistutils.cfg bugs
2987 # least on Windows for now, deal with .pydistutils.cfg bugs
2988 # when they happen.
2988 # when they happen.
2989 nohome = b''
2989 nohome = b''
2990 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2990 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2991 b' build %(compiler)s --build-base="%(base)s"'
2991 b' build %(compiler)s --build-base="%(base)s"'
2992 b' install --force --prefix="%(prefix)s"'
2992 b' install --force --prefix="%(prefix)s"'
2993 b' --install-lib="%(libdir)s"'
2993 b' --install-lib="%(libdir)s"'
2994 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2994 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2995 % {b'exe': exe, b'pure': pure,
2995 % {b'exe': exe, b'pure': pure,
2996 b'compiler': compiler,
2996 b'compiler': compiler,
2997 b'base': os.path.join(self._hgtmp, b"build"),
2997 b'base': os.path.join(self._hgtmp, b"build"),
2998 b'prefix': self._installdir, b'libdir': self._pythondir,
2998 b'prefix': self._installdir, b'libdir': self._pythondir,
2999 b'bindir': self._bindir,
2999 b'bindir': self._bindir,
3000 b'nohome': nohome, b'logfile': installerrs})
3000 b'nohome': nohome, b'logfile': installerrs})
3001
3001
3002 # setuptools requires install directories to exist.
3002 # setuptools requires install directories to exist.
3003 def makedirs(p):
3003 def makedirs(p):
3004 try:
3004 try:
3005 os.makedirs(p)
3005 os.makedirs(p)
3006 except OSError as e:
3006 except OSError as e:
3007 if e.errno != errno.EEXIST:
3007 if e.errno != errno.EEXIST:
3008 raise
3008 raise
3009 makedirs(self._pythondir)
3009 makedirs(self._pythondir)
3010 makedirs(self._bindir)
3010 makedirs(self._bindir)
3011
3011
3012 vlog("# Running", cmd)
3012 vlog("# Running", cmd)
3013 if os.system(_strpath(cmd)) == 0:
3013 if os.system(_strpath(cmd)) == 0:
3014 if not self.options.verbose:
3014 if not self.options.verbose:
3015 try:
3015 try:
3016 os.remove(installerrs)
3016 os.remove(installerrs)
3017 except OSError as e:
3017 except OSError as e:
3018 if e.errno != errno.ENOENT:
3018 if e.errno != errno.ENOENT:
3019 raise
3019 raise
3020 else:
3020 else:
3021 with open(installerrs, 'rb') as f:
3021 with open(installerrs, 'rb') as f:
3022 for line in f:
3022 for line in f:
3023 if PYTHON3:
3023 if PYTHON3:
3024 sys.stdout.buffer.write(line)
3024 sys.stdout.buffer.write(line)
3025 else:
3025 else:
3026 sys.stdout.write(line)
3026 sys.stdout.write(line)
3027 sys.exit(1)
3027 sys.exit(1)
3028 os.chdir(self._testdir)
3028 os.chdir(self._testdir)
3029
3029
3030 self._usecorrectpython()
3030 self._usecorrectpython()
3031
3031
3032 if self.options.py3k_warnings and not self.options.anycoverage:
3032 if self.options.py3_warnings and not self.options.anycoverage:
3033 vlog("# Updating hg command to enable Py3k Warnings switch")
3033 vlog("# Updating hg command to enable Py3k Warnings switch")
3034 with open(os.path.join(self._bindir, 'hg'), 'rb') as f:
3034 with open(os.path.join(self._bindir, 'hg'), 'rb') as f:
3035 lines = [line.rstrip() for line in f]
3035 lines = [line.rstrip() for line in f]
3036 lines[0] += ' -3'
3036 lines[0] += ' -3'
3037 with open(os.path.join(self._bindir, 'hg'), 'wb') as f:
3037 with open(os.path.join(self._bindir, 'hg'), 'wb') as f:
3038 for line in lines:
3038 for line in lines:
3039 f.write(line + '\n')
3039 f.write(line + '\n')
3040
3040
3041 hgbat = os.path.join(self._bindir, b'hg.bat')
3041 hgbat = os.path.join(self._bindir, b'hg.bat')
3042 if os.path.isfile(hgbat):
3042 if os.path.isfile(hgbat):
3043 # hg.bat expects to be put in bin/scripts while run-tests.py
3043 # hg.bat expects to be put in bin/scripts while run-tests.py
3044 # installation layout put it in bin/ directly. Fix it
3044 # installation layout put it in bin/ directly. Fix it
3045 with open(hgbat, 'rb') as f:
3045 with open(hgbat, 'rb') as f:
3046 data = f.read()
3046 data = f.read()
3047 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
3047 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
3048 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
3048 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
3049 b'"%~dp0python" "%~dp0hg" %*')
3049 b'"%~dp0python" "%~dp0hg" %*')
3050 with open(hgbat, 'wb') as f:
3050 with open(hgbat, 'wb') as f:
3051 f.write(data)
3051 f.write(data)
3052 else:
3052 else:
3053 print('WARNING: cannot fix hg.bat reference to python.exe')
3053 print('WARNING: cannot fix hg.bat reference to python.exe')
3054
3054
3055 if self.options.anycoverage:
3055 if self.options.anycoverage:
3056 custom = os.path.join(self._testdir, 'sitecustomize.py')
3056 custom = os.path.join(self._testdir, 'sitecustomize.py')
3057 target = os.path.join(self._pythondir, 'sitecustomize.py')
3057 target = os.path.join(self._pythondir, 'sitecustomize.py')
3058 vlog('# Installing coverage trigger to %s' % target)
3058 vlog('# Installing coverage trigger to %s' % target)
3059 shutil.copyfile(custom, target)
3059 shutil.copyfile(custom, target)
3060 rc = os.path.join(self._testdir, '.coveragerc')
3060 rc = os.path.join(self._testdir, '.coveragerc')
3061 vlog('# Installing coverage rc to %s' % rc)
3061 vlog('# Installing coverage rc to %s' % rc)
3062 os.environ['COVERAGE_PROCESS_START'] = rc
3062 os.environ['COVERAGE_PROCESS_START'] = rc
3063 covdir = os.path.join(self._installdir, '..', 'coverage')
3063 covdir = os.path.join(self._installdir, '..', 'coverage')
3064 try:
3064 try:
3065 os.mkdir(covdir)
3065 os.mkdir(covdir)
3066 except OSError as e:
3066 except OSError as e:
3067 if e.errno != errno.EEXIST:
3067 if e.errno != errno.EEXIST:
3068 raise
3068 raise
3069
3069
3070 os.environ['COVERAGE_DIR'] = covdir
3070 os.environ['COVERAGE_DIR'] = covdir
3071
3071
3072 def _checkhglib(self, verb):
3072 def _checkhglib(self, verb):
3073 """Ensure that the 'mercurial' package imported by python is
3073 """Ensure that the 'mercurial' package imported by python is
3074 the one we expect it to be. If not, print a warning to stderr."""
3074 the one we expect it to be. If not, print a warning to stderr."""
3075 if ((self._bindir == self._pythondir) and
3075 if ((self._bindir == self._pythondir) and
3076 (self._bindir != self._tmpbindir)):
3076 (self._bindir != self._tmpbindir)):
3077 # The pythondir has been inferred from --with-hg flag.
3077 # The pythondir has been inferred from --with-hg flag.
3078 # We cannot expect anything sensible here.
3078 # We cannot expect anything sensible here.
3079 return
3079 return
3080 expecthg = os.path.join(self._pythondir, b'mercurial')
3080 expecthg = os.path.join(self._pythondir, b'mercurial')
3081 actualhg = self._gethgpath()
3081 actualhg = self._gethgpath()
3082 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3082 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3083 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
3083 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
3084 ' (expected %s)\n'
3084 ' (expected %s)\n'
3085 % (verb, actualhg, expecthg))
3085 % (verb, actualhg, expecthg))
3086 def _gethgpath(self):
3086 def _gethgpath(self):
3087 """Return the path to the mercurial package that is actually found by
3087 """Return the path to the mercurial package that is actually found by
3088 the current Python interpreter."""
3088 the current Python interpreter."""
3089 if self._hgpath is not None:
3089 if self._hgpath is not None:
3090 return self._hgpath
3090 return self._hgpath
3091
3091
3092 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
3092 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
3093 cmd = cmd % PYTHON
3093 cmd = cmd % PYTHON
3094 if PYTHON3:
3094 if PYTHON3:
3095 cmd = _strpath(cmd)
3095 cmd = _strpath(cmd)
3096 pipe = os.popen(cmd)
3096 pipe = os.popen(cmd)
3097 try:
3097 try:
3098 self._hgpath = _bytespath(pipe.read().strip())
3098 self._hgpath = _bytespath(pipe.read().strip())
3099 finally:
3099 finally:
3100 pipe.close()
3100 pipe.close()
3101
3101
3102 return self._hgpath
3102 return self._hgpath
3103
3103
3104 def _installchg(self):
3104 def _installchg(self):
3105 """Install chg into the test environment"""
3105 """Install chg into the test environment"""
3106 vlog('# Performing temporary installation of CHG')
3106 vlog('# Performing temporary installation of CHG')
3107 assert os.path.dirname(self._bindir) == self._installdir
3107 assert os.path.dirname(self._bindir) == self._installdir
3108 assert self._hgroot, 'must be called after _installhg()'
3108 assert self._hgroot, 'must be called after _installhg()'
3109 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
3109 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
3110 % {b'make': 'make', # TODO: switch by option or environment?
3110 % {b'make': 'make', # TODO: switch by option or environment?
3111 b'prefix': self._installdir})
3111 b'prefix': self._installdir})
3112 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3112 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3113 vlog("# Running", cmd)
3113 vlog("# Running", cmd)
3114 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
3114 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
3115 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
3115 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
3116 stderr=subprocess.STDOUT)
3116 stderr=subprocess.STDOUT)
3117 out, _err = proc.communicate()
3117 out, _err = proc.communicate()
3118 if proc.returncode != 0:
3118 if proc.returncode != 0:
3119 if PYTHON3:
3119 if PYTHON3:
3120 sys.stdout.buffer.write(out)
3120 sys.stdout.buffer.write(out)
3121 else:
3121 else:
3122 sys.stdout.write(out)
3122 sys.stdout.write(out)
3123 sys.exit(1)
3123 sys.exit(1)
3124
3124
3125 def _outputcoverage(self):
3125 def _outputcoverage(self):
3126 """Produce code coverage output."""
3126 """Produce code coverage output."""
3127 import coverage
3127 import coverage
3128 coverage = coverage.coverage
3128 coverage = coverage.coverage
3129
3129
3130 vlog('# Producing coverage report')
3130 vlog('# Producing coverage report')
3131 # chdir is the easiest way to get short, relative paths in the
3131 # chdir is the easiest way to get short, relative paths in the
3132 # output.
3132 # output.
3133 os.chdir(self._hgroot)
3133 os.chdir(self._hgroot)
3134 covdir = os.path.join(self._installdir, '..', 'coverage')
3134 covdir = os.path.join(self._installdir, '..', 'coverage')
3135 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3135 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3136
3136
3137 # Map install directory paths back to source directory.
3137 # Map install directory paths back to source directory.
3138 cov.config.paths['srcdir'] = ['.', self._pythondir]
3138 cov.config.paths['srcdir'] = ['.', self._pythondir]
3139
3139
3140 cov.combine()
3140 cov.combine()
3141
3141
3142 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
3142 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
3143 cov.report(ignore_errors=True, omit=omit)
3143 cov.report(ignore_errors=True, omit=omit)
3144
3144
3145 if self.options.htmlcov:
3145 if self.options.htmlcov:
3146 htmldir = os.path.join(self._outputdir, 'htmlcov')
3146 htmldir = os.path.join(self._outputdir, 'htmlcov')
3147 cov.html_report(directory=htmldir, omit=omit)
3147 cov.html_report(directory=htmldir, omit=omit)
3148 if self.options.annotate:
3148 if self.options.annotate:
3149 adir = os.path.join(self._outputdir, 'annotated')
3149 adir = os.path.join(self._outputdir, 'annotated')
3150 if not os.path.isdir(adir):
3150 if not os.path.isdir(adir):
3151 os.mkdir(adir)
3151 os.mkdir(adir)
3152 cov.annotate(directory=adir, omit=omit)
3152 cov.annotate(directory=adir, omit=omit)
3153
3153
3154 def _findprogram(self, program):
3154 def _findprogram(self, program):
3155 """Search PATH for a executable program"""
3155 """Search PATH for a executable program"""
3156 dpb = _bytespath(os.defpath)
3156 dpb = _bytespath(os.defpath)
3157 sepb = _bytespath(os.pathsep)
3157 sepb = _bytespath(os.pathsep)
3158 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3158 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3159 name = os.path.join(p, program)
3159 name = os.path.join(p, program)
3160 if os.name == 'nt' or os.access(name, os.X_OK):
3160 if os.name == 'nt' or os.access(name, os.X_OK):
3161 return name
3161 return name
3162 return None
3162 return None
3163
3163
3164 def _checktools(self):
3164 def _checktools(self):
3165 """Ensure tools required to run tests are present."""
3165 """Ensure tools required to run tests are present."""
3166 for p in self.REQUIREDTOOLS:
3166 for p in self.REQUIREDTOOLS:
3167 if os.name == 'nt' and not p.endswith(b'.exe'):
3167 if os.name == 'nt' and not p.endswith(b'.exe'):
3168 p += b'.exe'
3168 p += b'.exe'
3169 found = self._findprogram(p)
3169 found = self._findprogram(p)
3170 if found:
3170 if found:
3171 vlog("# Found prerequisite", p, "at", found)
3171 vlog("# Found prerequisite", p, "at", found)
3172 else:
3172 else:
3173 print("WARNING: Did not find prerequisite tool: %s " %
3173 print("WARNING: Did not find prerequisite tool: %s " %
3174 p.decode("utf-8"))
3174 p.decode("utf-8"))
3175
3175
3176 def aggregateexceptions(path):
3176 def aggregateexceptions(path):
3177 exceptioncounts = collections.Counter()
3177 exceptioncounts = collections.Counter()
3178 testsbyfailure = collections.defaultdict(set)
3178 testsbyfailure = collections.defaultdict(set)
3179 failuresbytest = collections.defaultdict(set)
3179 failuresbytest = collections.defaultdict(set)
3180
3180
3181 for f in os.listdir(path):
3181 for f in os.listdir(path):
3182 with open(os.path.join(path, f), 'rb') as fh:
3182 with open(os.path.join(path, f), 'rb') as fh:
3183 data = fh.read().split(b'\0')
3183 data = fh.read().split(b'\0')
3184 if len(data) != 5:
3184 if len(data) != 5:
3185 continue
3185 continue
3186
3186
3187 exc, mainframe, hgframe, hgline, testname = data
3187 exc, mainframe, hgframe, hgline, testname = data
3188 exc = exc.decode('utf-8')
3188 exc = exc.decode('utf-8')
3189 mainframe = mainframe.decode('utf-8')
3189 mainframe = mainframe.decode('utf-8')
3190 hgframe = hgframe.decode('utf-8')
3190 hgframe = hgframe.decode('utf-8')
3191 hgline = hgline.decode('utf-8')
3191 hgline = hgline.decode('utf-8')
3192 testname = testname.decode('utf-8')
3192 testname = testname.decode('utf-8')
3193
3193
3194 key = (hgframe, hgline, exc)
3194 key = (hgframe, hgline, exc)
3195 exceptioncounts[key] += 1
3195 exceptioncounts[key] += 1
3196 testsbyfailure[key].add(testname)
3196 testsbyfailure[key].add(testname)
3197 failuresbytest[testname].add(key)
3197 failuresbytest[testname].add(key)
3198
3198
3199 # Find test having fewest failures for each failure.
3199 # Find test having fewest failures for each failure.
3200 leastfailing = {}
3200 leastfailing = {}
3201 for key, tests in testsbyfailure.items():
3201 for key, tests in testsbyfailure.items():
3202 fewesttest = None
3202 fewesttest = None
3203 fewestcount = 99999999
3203 fewestcount = 99999999
3204 for test in sorted(tests):
3204 for test in sorted(tests):
3205 if len(failuresbytest[test]) < fewestcount:
3205 if len(failuresbytest[test]) < fewestcount:
3206 fewesttest = test
3206 fewesttest = test
3207 fewestcount = len(failuresbytest[test])
3207 fewestcount = len(failuresbytest[test])
3208
3208
3209 leastfailing[key] = (fewestcount, fewesttest)
3209 leastfailing[key] = (fewestcount, fewesttest)
3210
3210
3211 # Create a combined counter so we can sort by total occurrences and
3211 # Create a combined counter so we can sort by total occurrences and
3212 # impacted tests.
3212 # impacted tests.
3213 combined = {}
3213 combined = {}
3214 for key in exceptioncounts:
3214 for key in exceptioncounts:
3215 combined[key] = (exceptioncounts[key],
3215 combined[key] = (exceptioncounts[key],
3216 len(testsbyfailure[key]),
3216 len(testsbyfailure[key]),
3217 leastfailing[key][0],
3217 leastfailing[key][0],
3218 leastfailing[key][1])
3218 leastfailing[key][1])
3219
3219
3220 return {
3220 return {
3221 'exceptioncounts': exceptioncounts,
3221 'exceptioncounts': exceptioncounts,
3222 'total': sum(exceptioncounts.values()),
3222 'total': sum(exceptioncounts.values()),
3223 'combined': combined,
3223 'combined': combined,
3224 'leastfailing': leastfailing,
3224 'leastfailing': leastfailing,
3225 'byfailure': testsbyfailure,
3225 'byfailure': testsbyfailure,
3226 'bytest': failuresbytest,
3226 'bytest': failuresbytest,
3227 }
3227 }
3228
3228
3229 if __name__ == '__main__':
3229 if __name__ == '__main__':
3230 runner = TestRunner()
3230 runner = TestRunner()
3231
3231
3232 try:
3232 try:
3233 import msvcrt
3233 import msvcrt
3234 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3234 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3235 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3235 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3236 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3236 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3237 except ImportError:
3237 except ImportError:
3238 pass
3238 pass
3239
3239
3240 sys.exit(runner.run(sys.argv[1:]))
3240 sys.exit(runner.run(sys.argv[1:]))
@@ -1,611 +1,611 b''
1 #require serve
1 #require serve
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo foo>foo
5 $ echo foo>foo
6 $ hg commit -Am 1 -d '1 0'
6 $ hg commit -Am 1 -d '1 0'
7 adding foo
7 adding foo
8 $ echo bar>bar
8 $ echo bar>bar
9 $ hg commit -Am 2 -d '2 0'
9 $ hg commit -Am 2 -d '2 0'
10 adding bar
10 adding bar
11 $ mkdir baz
11 $ mkdir baz
12 $ echo bletch>baz/bletch
12 $ echo bletch>baz/bletch
13 $ hg commit -Am 3 -d '1000000000 0'
13 $ hg commit -Am 3 -d '1000000000 0'
14 adding baz/bletch
14 adding baz/bletch
15 $ hg init subrepo
15 $ hg init subrepo
16 $ touch subrepo/sub
16 $ touch subrepo/sub
17 $ hg -q -R subrepo ci -Am "init subrepo"
17 $ hg -q -R subrepo ci -Am "init subrepo"
18 $ echo "subrepo = subrepo" > .hgsub
18 $ echo "subrepo = subrepo" > .hgsub
19 $ hg add .hgsub
19 $ hg add .hgsub
20 $ hg ci -m "add subrepo"
20 $ hg ci -m "add subrepo"
21
21
22 $ cat >> $HGRCPATH <<EOF
22 $ cat >> $HGRCPATH <<EOF
23 > [extensions]
23 > [extensions]
24 > share =
24 > share =
25 > EOF
25 > EOF
26
26
27 hg subrepos are shared when the parent repo is shared
27 hg subrepos are shared when the parent repo is shared
28
28
29 $ cd ..
29 $ cd ..
30 $ hg share test shared1
30 $ hg share test shared1
31 updating working directory
31 updating working directory
32 sharing subrepo subrepo from $TESTTMP/test/subrepo
32 sharing subrepo subrepo from $TESTTMP/test/subrepo
33 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 $ cat shared1/subrepo/.hg/sharedpath
34 $ cat shared1/subrepo/.hg/sharedpath
35 $TESTTMP/test/subrepo/.hg (no-eol)
35 $TESTTMP/test/subrepo/.hg (no-eol)
36
36
37 hg subrepos are shared into existence on demand if the parent was shared
37 hg subrepos are shared into existence on demand if the parent was shared
38
38
39 $ hg clone -qr 1 test clone1
39 $ hg clone -qr 1 test clone1
40 $ hg share clone1 share2
40 $ hg share clone1 share2
41 updating working directory
41 updating working directory
42 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 $ hg -R clone1 -q pull
43 $ hg -R clone1 -q pull
44 $ hg -R share2 update tip
44 $ hg -R share2 update tip
45 sharing subrepo subrepo from $TESTTMP/test/subrepo
45 sharing subrepo subrepo from $TESTTMP/test/subrepo
46 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
47 $ cat share2/subrepo/.hg/sharedpath
47 $ cat share2/subrepo/.hg/sharedpath
48 $TESTTMP/test/subrepo/.hg (no-eol)
48 $TESTTMP/test/subrepo/.hg (no-eol)
49 $ echo 'mod' > share2/subrepo/sub
49 $ echo 'mod' > share2/subrepo/sub
50 $ hg -R share2 ci -Sqm 'subrepo mod'
50 $ hg -R share2 ci -Sqm 'subrepo mod'
51 $ hg -R clone1 update -C tip
51 $ hg -R clone1 update -C tip
52 cloning subrepo subrepo from $TESTTMP/test/subrepo
52 cloning subrepo subrepo from $TESTTMP/test/subrepo
53 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 $ find share2 | egrep 'sharedpath|00.+\.i' | sort
54 $ find share2 | egrep 'sharedpath|00.+\.i' | sort
55 share2/.hg/sharedpath
55 share2/.hg/sharedpath
56 share2/subrepo/.hg/sharedpath
56 share2/subrepo/.hg/sharedpath
57 $ hg -R share2 unshare
57 $ hg -R share2 unshare
58 unsharing subrepo 'subrepo'
58 unsharing subrepo 'subrepo'
59 $ find share2 | egrep 'sharedpath|00.+\.i' | sort
59 $ find share2 | egrep 'sharedpath|00.+\.i' | sort
60 share2/.hg/00changelog.i
60 share2/.hg/00changelog.i
61 share2/.hg/sharedpath.old
61 share2/.hg/sharedpath.old
62 share2/.hg/store/00changelog.i
62 share2/.hg/store/00changelog.i
63 share2/.hg/store/00manifest.i
63 share2/.hg/store/00manifest.i
64 share2/subrepo/.hg/00changelog.i
64 share2/subrepo/.hg/00changelog.i
65 share2/subrepo/.hg/sharedpath.old
65 share2/subrepo/.hg/sharedpath.old
66 share2/subrepo/.hg/store/00changelog.i
66 share2/subrepo/.hg/store/00changelog.i
67 share2/subrepo/.hg/store/00manifest.i
67 share2/subrepo/.hg/store/00manifest.i
68 $ hg -R share2/subrepo log -r tip -T compact
68 $ hg -R share2/subrepo log -r tip -T compact
69 1[tip] 559dcc9bfa65 1970-01-01 00:00 +0000 test
69 1[tip] 559dcc9bfa65 1970-01-01 00:00 +0000 test
70 subrepo mod
70 subrepo mod
71
71
72 $ rm -rf clone1
72 $ rm -rf clone1
73
73
74 $ hg clone -qr 1 test clone1
74 $ hg clone -qr 1 test clone1
75 $ hg share clone1 shared3
75 $ hg share clone1 shared3
76 updating working directory
76 updating working directory
77 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
77 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 $ hg -R clone1 -q pull
78 $ hg -R clone1 -q pull
79 $ hg -R shared3 archive --config ui.archivemeta=False -r tip -S archive
79 $ hg -R shared3 archive --config ui.archivemeta=False -r tip -S archive
80 sharing subrepo subrepo from $TESTTMP/test/subrepo
80 sharing subrepo subrepo from $TESTTMP/test/subrepo
81 $ cat shared3/subrepo/.hg/sharedpath
81 $ cat shared3/subrepo/.hg/sharedpath
82 $TESTTMP/test/subrepo/.hg (no-eol)
82 $TESTTMP/test/subrepo/.hg (no-eol)
83 $ diff -r archive test
83 $ diff -r archive test
84 Only in test: .hg
84 Only in test: .hg
85 Common subdirectories: archive/baz and test/baz (?)
85 Common subdirectories: archive/baz and test/baz (?)
86 Common subdirectories: archive/subrepo and test/subrepo (?)
86 Common subdirectories: archive/subrepo and test/subrepo (?)
87 Only in test/subrepo: .hg
87 Only in test/subrepo: .hg
88 [1]
88 [1]
89 $ rm -rf archive
89 $ rm -rf archive
90
90
91 $ cd test
91 $ cd test
92 $ echo "[web]" >> .hg/hgrc
92 $ echo "[web]" >> .hg/hgrc
93 $ echo "name = test-archive" >> .hg/hgrc
93 $ echo "name = test-archive" >> .hg/hgrc
94 $ echo "archivesubrepos = True" >> .hg/hgrc
94 $ echo "archivesubrepos = True" >> .hg/hgrc
95 $ cp .hg/hgrc .hg/hgrc-base
95 $ cp .hg/hgrc .hg/hgrc-base
96 > test_archtype() {
96 > test_archtype() {
97 > echo "allow-archive = $1" >> .hg/hgrc
97 > echo "allow-archive = $1" >> .hg/hgrc
98 > test_archtype_run "$@"
98 > test_archtype_run "$@"
99 > }
99 > }
100 > test_archtype_deprecated() {
100 > test_archtype_deprecated() {
101 > echo "allow$1 = True" >> .hg/hgrc
101 > echo "allow$1 = True" >> .hg/hgrc
102 > test_archtype_run "$@"
102 > test_archtype_run "$@"
103 > }
103 > }
104 > test_archtype_run() {
104 > test_archtype_run() {
105 > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log \
105 > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log \
106 > --config extensions.blackbox= --config blackbox.track=develwarn
106 > --config extensions.blackbox= --config blackbox.track=develwarn
107 > cat hg.pid >> $DAEMON_PIDS
107 > cat hg.pid >> $DAEMON_PIDS
108 > echo % $1 allowed should give 200
108 > echo % $1 allowed should give 200
109 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$2" -
109 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$2" -
110 > f --size --sha1 body
110 > f --size --sha1 body
111 > echo % $3 and $4 disallowed should both give 403
111 > echo % $3 and $4 disallowed should both give 403
112 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$3" -
112 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$3" -
113 > f --size --sha1 body
113 > f --size --sha1 body
114 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$4" -
114 > get-with-headers.py --bodyfile body localhost:$HGPORT "archive/tip.$4" -
115 > f --size --sha1 body
115 > f --size --sha1 body
116 > killdaemons.py
116 > killdaemons.py
117 > cat errors.log
117 > cat errors.log
118 > hg blackbox --config extensions.blackbox= --config blackbox.track=
118 > hg blackbox --config extensions.blackbox= --config blackbox.track=
119 > cp .hg/hgrc-base .hg/hgrc
119 > cp .hg/hgrc-base .hg/hgrc
120 > }
120 > }
121
121
122 check http return codes
122 check http return codes
123
123
124 $ test_archtype gz tar.gz tar.bz2 zip
124 $ test_archtype gz tar.gz tar.bz2 zip
125 % gz allowed should give 200
125 % gz allowed should give 200
126 200 Script output follows
126 200 Script output follows
127 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.gz
127 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.gz
128 content-type: application/x-gzip
128 content-type: application/x-gzip
129 date: $HTTP_DATE$
129 date: $HTTP_DATE$
130 etag: W/"*" (glob)
130 etag: W/"*" (glob)
131 server: testing stub value
131 server: testing stub value
132 transfer-encoding: chunked
132 transfer-encoding: chunked
133
133
134 body: size=408, sha1=8fa06531bddecc365a9f5edb0f88b65974bfe505
134 body: size=408, sha1=8fa06531bddecc365a9f5edb0f88b65974bfe505
135 % tar.bz2 and zip disallowed should both give 403
135 % tar.bz2 and zip disallowed should both give 403
136 403 Archive type not allowed: bz2
136 403 Archive type not allowed: bz2
137 content-type: text/html; charset=ascii
137 content-type: text/html; charset=ascii
138 date: $HTTP_DATE$
138 date: $HTTP_DATE$
139 etag: W/"*" (glob)
139 etag: W/"*" (glob)
140 server: testing stub value
140 server: testing stub value
141 transfer-encoding: chunked
141 transfer-encoding: chunked
142
142
143 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
143 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
144 403 Archive type not allowed: zip
144 403 Archive type not allowed: zip
145 content-type: text/html; charset=ascii
145 content-type: text/html; charset=ascii
146 date: $HTTP_DATE$
146 date: $HTTP_DATE$
147 etag: W/"*" (glob)
147 etag: W/"*" (glob)
148 server: testing stub value
148 server: testing stub value
149 transfer-encoding: chunked
149 transfer-encoding: chunked
150
150
151 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
151 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
152 $ test_archtype bz2 tar.bz2 zip tar.gz
152 $ test_archtype bz2 tar.bz2 zip tar.gz
153 % bz2 allowed should give 200
153 % bz2 allowed should give 200
154 200 Script output follows
154 200 Script output follows
155 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.bz2
155 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.bz2
156 content-type: application/x-bzip2
156 content-type: application/x-bzip2
157 date: $HTTP_DATE$
157 date: $HTTP_DATE$
158 etag: W/"*" (glob)
158 etag: W/"*" (glob)
159 server: testing stub value
159 server: testing stub value
160 transfer-encoding: chunked
160 transfer-encoding: chunked
161
161
162 body: size=426, sha1=8d87f5aba6e14f1bfea6c232985982c278b2fb0b
162 body: size=426, sha1=8d87f5aba6e14f1bfea6c232985982c278b2fb0b
163 % zip and tar.gz disallowed should both give 403
163 % zip and tar.gz disallowed should both give 403
164 403 Archive type not allowed: zip
164 403 Archive type not allowed: zip
165 content-type: text/html; charset=ascii
165 content-type: text/html; charset=ascii
166 date: $HTTP_DATE$
166 date: $HTTP_DATE$
167 etag: W/"*" (glob)
167 etag: W/"*" (glob)
168 server: testing stub value
168 server: testing stub value
169 transfer-encoding: chunked
169 transfer-encoding: chunked
170
170
171 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
171 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
172 403 Archive type not allowed: gz
172 403 Archive type not allowed: gz
173 content-type: text/html; charset=ascii
173 content-type: text/html; charset=ascii
174 date: $HTTP_DATE$
174 date: $HTTP_DATE$
175 etag: W/"*" (glob)
175 etag: W/"*" (glob)
176 server: testing stub value
176 server: testing stub value
177 transfer-encoding: chunked
177 transfer-encoding: chunked
178
178
179 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
179 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
180 $ test_archtype zip zip tar.gz tar.bz2
180 $ test_archtype zip zip tar.gz tar.bz2
181 % zip allowed should give 200
181 % zip allowed should give 200
182 200 Script output follows
182 200 Script output follows
183 content-disposition: attachment; filename=test-archive-1701ef1f1510.zip
183 content-disposition: attachment; filename=test-archive-1701ef1f1510.zip
184 content-type: application/zip
184 content-type: application/zip
185 date: $HTTP_DATE$
185 date: $HTTP_DATE$
186 etag: W/"*" (glob)
186 etag: W/"*" (glob)
187 server: testing stub value
187 server: testing stub value
188 transfer-encoding: chunked
188 transfer-encoding: chunked
189
189
190 body: size=1377, sha1=677b14d3d048778d5eb5552c14a67e6192068650 (no-py3k !)
190 body: size=1377, sha1=677b14d3d048778d5eb5552c14a67e6192068650 (no-py3 !)
191 body: size=1461, sha1=be6d3983aa13dfe930361b2569291cdedd02b537 (py3k !)
191 body: size=1461, sha1=be6d3983aa13dfe930361b2569291cdedd02b537 (py3 !)
192 % tar.gz and tar.bz2 disallowed should both give 403
192 % tar.gz and tar.bz2 disallowed should both give 403
193 403 Archive type not allowed: gz
193 403 Archive type not allowed: gz
194 content-type: text/html; charset=ascii
194 content-type: text/html; charset=ascii
195 date: $HTTP_DATE$
195 date: $HTTP_DATE$
196 etag: W/"*" (glob)
196 etag: W/"*" (glob)
197 server: testing stub value
197 server: testing stub value
198 transfer-encoding: chunked
198 transfer-encoding: chunked
199
199
200 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
200 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
201 403 Archive type not allowed: bz2
201 403 Archive type not allowed: bz2
202 content-type: text/html; charset=ascii
202 content-type: text/html; charset=ascii
203 date: $HTTP_DATE$
203 date: $HTTP_DATE$
204 etag: W/"*" (glob)
204 etag: W/"*" (glob)
205 server: testing stub value
205 server: testing stub value
206 transfer-encoding: chunked
206 transfer-encoding: chunked
207
207
208 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
208 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
209
209
210 check http return codes (with deprecated option)
210 check http return codes (with deprecated option)
211
211
212 $ test_archtype_deprecated gz tar.gz tar.bz2 zip
212 $ test_archtype_deprecated gz tar.gz tar.bz2 zip
213 % gz allowed should give 200
213 % gz allowed should give 200
214 200 Script output follows
214 200 Script output follows
215 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.gz
215 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.gz
216 content-type: application/x-gzip
216 content-type: application/x-gzip
217 date: $HTTP_DATE$
217 date: $HTTP_DATE$
218 etag: W/"*" (glob)
218 etag: W/"*" (glob)
219 server: testing stub value
219 server: testing stub value
220 transfer-encoding: chunked
220 transfer-encoding: chunked
221
221
222 body: size=408, sha1=8fa06531bddecc365a9f5edb0f88b65974bfe505
222 body: size=408, sha1=8fa06531bddecc365a9f5edb0f88b65974bfe505
223 % tar.bz2 and zip disallowed should both give 403
223 % tar.bz2 and zip disallowed should both give 403
224 403 Archive type not allowed: bz2
224 403 Archive type not allowed: bz2
225 content-type: text/html; charset=ascii
225 content-type: text/html; charset=ascii
226 date: $HTTP_DATE$
226 date: $HTTP_DATE$
227 etag: W/"*" (glob)
227 etag: W/"*" (glob)
228 server: testing stub value
228 server: testing stub value
229 transfer-encoding: chunked
229 transfer-encoding: chunked
230
230
231 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
231 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
232 403 Archive type not allowed: zip
232 403 Archive type not allowed: zip
233 content-type: text/html; charset=ascii
233 content-type: text/html; charset=ascii
234 date: $HTTP_DATE$
234 date: $HTTP_DATE$
235 etag: W/"*" (glob)
235 etag: W/"*" (glob)
236 server: testing stub value
236 server: testing stub value
237 transfer-encoding: chunked
237 transfer-encoding: chunked
238
238
239 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
239 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
240 $ test_archtype_deprecated bz2 tar.bz2 zip tar.gz
240 $ test_archtype_deprecated bz2 tar.bz2 zip tar.gz
241 % bz2 allowed should give 200
241 % bz2 allowed should give 200
242 200 Script output follows
242 200 Script output follows
243 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.bz2
243 content-disposition: attachment; filename=test-archive-1701ef1f1510.tar.bz2
244 content-type: application/x-bzip2
244 content-type: application/x-bzip2
245 date: $HTTP_DATE$
245 date: $HTTP_DATE$
246 etag: W/"*" (glob)
246 etag: W/"*" (glob)
247 server: testing stub value
247 server: testing stub value
248 transfer-encoding: chunked
248 transfer-encoding: chunked
249
249
250 body: size=426, sha1=8d87f5aba6e14f1bfea6c232985982c278b2fb0b
250 body: size=426, sha1=8d87f5aba6e14f1bfea6c232985982c278b2fb0b
251 % zip and tar.gz disallowed should both give 403
251 % zip and tar.gz disallowed should both give 403
252 403 Archive type not allowed: zip
252 403 Archive type not allowed: zip
253 content-type: text/html; charset=ascii
253 content-type: text/html; charset=ascii
254 date: $HTTP_DATE$
254 date: $HTTP_DATE$
255 etag: W/"*" (glob)
255 etag: W/"*" (glob)
256 server: testing stub value
256 server: testing stub value
257 transfer-encoding: chunked
257 transfer-encoding: chunked
258
258
259 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
259 body: size=1451, sha1=cbfa5574b337348bfd0564cc534474d002e7d6c7
260 403 Archive type not allowed: gz
260 403 Archive type not allowed: gz
261 content-type: text/html; charset=ascii
261 content-type: text/html; charset=ascii
262 date: $HTTP_DATE$
262 date: $HTTP_DATE$
263 etag: W/"*" (glob)
263 etag: W/"*" (glob)
264 server: testing stub value
264 server: testing stub value
265 transfer-encoding: chunked
265 transfer-encoding: chunked
266
266
267 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
267 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
268 $ test_archtype_deprecated zip zip tar.gz tar.bz2
268 $ test_archtype_deprecated zip zip tar.gz tar.bz2
269 % zip allowed should give 200
269 % zip allowed should give 200
270 200 Script output follows
270 200 Script output follows
271 content-disposition: attachment; filename=test-archive-1701ef1f1510.zip
271 content-disposition: attachment; filename=test-archive-1701ef1f1510.zip
272 content-type: application/zip
272 content-type: application/zip
273 date: $HTTP_DATE$
273 date: $HTTP_DATE$
274 etag: W/"*" (glob)
274 etag: W/"*" (glob)
275 server: testing stub value
275 server: testing stub value
276 transfer-encoding: chunked
276 transfer-encoding: chunked
277
277
278 body: size=1377, sha1=677b14d3d048778d5eb5552c14a67e6192068650 (no-py3k !)
278 body: size=1377, sha1=677b14d3d048778d5eb5552c14a67e6192068650 (no-py3 !)
279 body: size=1461, sha1=be6d3983aa13dfe930361b2569291cdedd02b537 (py3k !)
279 body: size=1461, sha1=be6d3983aa13dfe930361b2569291cdedd02b537 (py3 !)
280 % tar.gz and tar.bz2 disallowed should both give 403
280 % tar.gz and tar.bz2 disallowed should both give 403
281 403 Archive type not allowed: gz
281 403 Archive type not allowed: gz
282 content-type: text/html; charset=ascii
282 content-type: text/html; charset=ascii
283 date: $HTTP_DATE$
283 date: $HTTP_DATE$
284 etag: W/"*" (glob)
284 etag: W/"*" (glob)
285 server: testing stub value
285 server: testing stub value
286 transfer-encoding: chunked
286 transfer-encoding: chunked
287
287
288 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
288 body: size=1450, sha1=71f0b12d59f85fdcfe8ff493e2dc66863f2f7734
289 403 Archive type not allowed: bz2
289 403 Archive type not allowed: bz2
290 content-type: text/html; charset=ascii
290 content-type: text/html; charset=ascii
291 date: $HTTP_DATE$
291 date: $HTTP_DATE$
292 etag: W/"*" (glob)
292 etag: W/"*" (glob)
293 server: testing stub value
293 server: testing stub value
294 transfer-encoding: chunked
294 transfer-encoding: chunked
295
295
296 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
296 body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
297
297
298 $ echo "allow-archive = gz bz2 zip" >> .hg/hgrc
298 $ echo "allow-archive = gz bz2 zip" >> .hg/hgrc
299 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
299 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
300 $ cat hg.pid >> $DAEMON_PIDS
300 $ cat hg.pid >> $DAEMON_PIDS
301
301
302 check archive links' order
302 check archive links' order
303
303
304 $ get-with-headers.py localhost:$HGPORT "?revcount=1" | grep '/archive/tip.'
304 $ get-with-headers.py localhost:$HGPORT "?revcount=1" | grep '/archive/tip.'
305 <a href="/archive/tip.zip">zip</a>
305 <a href="/archive/tip.zip">zip</a>
306 <a href="/archive/tip.tar.gz">gz</a>
306 <a href="/archive/tip.tar.gz">gz</a>
307 <a href="/archive/tip.tar.bz2">bz2</a>
307 <a href="/archive/tip.tar.bz2">bz2</a>
308
308
309 invalid arch type should give 404
309 invalid arch type should give 404
310
310
311 $ get-with-headers.py localhost:$HGPORT "archive/tip.invalid" | head -n 1
311 $ get-with-headers.py localhost:$HGPORT "archive/tip.invalid" | head -n 1
312 404 Unsupported archive type: None
312 404 Unsupported archive type: None
313
313
314 $ TIP=`hg id -v | cut -f1 -d' '`
314 $ TIP=`hg id -v | cut -f1 -d' '`
315 $ QTIP=`hg id -q`
315 $ QTIP=`hg id -q`
316 $ cat > getarchive.py <<EOF
316 $ cat > getarchive.py <<EOF
317 > from __future__ import absolute_import
317 > from __future__ import absolute_import
318 > import os
318 > import os
319 > import sys
319 > import sys
320 > from mercurial import (
320 > from mercurial import (
321 > util,
321 > util,
322 > )
322 > )
323 > try:
323 > try:
324 > # Set stdout to binary mode for win32 platforms
324 > # Set stdout to binary mode for win32 platforms
325 > import msvcrt
325 > import msvcrt
326 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
326 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
327 > except ImportError:
327 > except ImportError:
328 > pass
328 > pass
329 > if len(sys.argv) <= 3:
329 > if len(sys.argv) <= 3:
330 > node, archive = sys.argv[1:]
330 > node, archive = sys.argv[1:]
331 > requeststr = 'cmd=archive;node=%s;type=%s' % (node, archive)
331 > requeststr = 'cmd=archive;node=%s;type=%s' % (node, archive)
332 > else:
332 > else:
333 > node, archive, file = sys.argv[1:]
333 > node, archive, file = sys.argv[1:]
334 > requeststr = 'cmd=archive;node=%s;type=%s;file=%s' % (node, archive, file)
334 > requeststr = 'cmd=archive;node=%s;type=%s;file=%s' % (node, archive, file)
335 > try:
335 > try:
336 > stdout = sys.stdout.buffer
336 > stdout = sys.stdout.buffer
337 > except AttributeError:
337 > except AttributeError:
338 > stdout = sys.stdout
338 > stdout = sys.stdout
339 > try:
339 > try:
340 > f = util.urlreq.urlopen('http://$LOCALIP:%s/?%s'
340 > f = util.urlreq.urlopen('http://$LOCALIP:%s/?%s'
341 > % (os.environ['HGPORT'], requeststr))
341 > % (os.environ['HGPORT'], requeststr))
342 > stdout.write(f.read())
342 > stdout.write(f.read())
343 > except util.urlerr.httperror as e:
343 > except util.urlerr.httperror as e:
344 > sys.stderr.write(str(e) + '\n')
344 > sys.stderr.write(str(e) + '\n')
345 > EOF
345 > EOF
346 $ "$PYTHON" getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null
346 $ "$PYTHON" getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null
347 test-archive-1701ef1f1510/.hg_archival.txt
347 test-archive-1701ef1f1510/.hg_archival.txt
348 test-archive-1701ef1f1510/.hgsub
348 test-archive-1701ef1f1510/.hgsub
349 test-archive-1701ef1f1510/.hgsubstate
349 test-archive-1701ef1f1510/.hgsubstate
350 test-archive-1701ef1f1510/bar
350 test-archive-1701ef1f1510/bar
351 test-archive-1701ef1f1510/baz/bletch
351 test-archive-1701ef1f1510/baz/bletch
352 test-archive-1701ef1f1510/foo
352 test-archive-1701ef1f1510/foo
353 test-archive-1701ef1f1510/subrepo/sub
353 test-archive-1701ef1f1510/subrepo/sub
354 $ "$PYTHON" getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null
354 $ "$PYTHON" getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null
355 test-archive-1701ef1f1510/.hg_archival.txt
355 test-archive-1701ef1f1510/.hg_archival.txt
356 test-archive-1701ef1f1510/.hgsub
356 test-archive-1701ef1f1510/.hgsub
357 test-archive-1701ef1f1510/.hgsubstate
357 test-archive-1701ef1f1510/.hgsubstate
358 test-archive-1701ef1f1510/bar
358 test-archive-1701ef1f1510/bar
359 test-archive-1701ef1f1510/baz/bletch
359 test-archive-1701ef1f1510/baz/bletch
360 test-archive-1701ef1f1510/foo
360 test-archive-1701ef1f1510/foo
361 test-archive-1701ef1f1510/subrepo/sub
361 test-archive-1701ef1f1510/subrepo/sub
362 $ "$PYTHON" getarchive.py "$TIP" zip > archive.zip
362 $ "$PYTHON" getarchive.py "$TIP" zip > archive.zip
363 $ unzip -t archive.zip
363 $ unzip -t archive.zip
364 Archive: archive.zip
364 Archive: archive.zip
365 testing: test-archive-1701ef1f1510/.hg_archival.txt*OK (glob)
365 testing: test-archive-1701ef1f1510/.hg_archival.txt*OK (glob)
366 testing: test-archive-1701ef1f1510/.hgsub*OK (glob)
366 testing: test-archive-1701ef1f1510/.hgsub*OK (glob)
367 testing: test-archive-1701ef1f1510/.hgsubstate*OK (glob)
367 testing: test-archive-1701ef1f1510/.hgsubstate*OK (glob)
368 testing: test-archive-1701ef1f1510/bar*OK (glob)
368 testing: test-archive-1701ef1f1510/bar*OK (glob)
369 testing: test-archive-1701ef1f1510/baz/bletch*OK (glob)
369 testing: test-archive-1701ef1f1510/baz/bletch*OK (glob)
370 testing: test-archive-1701ef1f1510/foo*OK (glob)
370 testing: test-archive-1701ef1f1510/foo*OK (glob)
371 testing: test-archive-1701ef1f1510/subrepo/sub*OK (glob)
371 testing: test-archive-1701ef1f1510/subrepo/sub*OK (glob)
372 No errors detected in compressed data of archive.zip.
372 No errors detected in compressed data of archive.zip.
373
373
374 test that we can download single directories and files
374 test that we can download single directories and files
375
375
376 $ "$PYTHON" getarchive.py "$TIP" gz baz | gunzip | tar tf - 2>/dev/null
376 $ "$PYTHON" getarchive.py "$TIP" gz baz | gunzip | tar tf - 2>/dev/null
377 test-archive-1701ef1f1510/baz/bletch
377 test-archive-1701ef1f1510/baz/bletch
378 $ "$PYTHON" getarchive.py "$TIP" gz foo | gunzip | tar tf - 2>/dev/null
378 $ "$PYTHON" getarchive.py "$TIP" gz foo | gunzip | tar tf - 2>/dev/null
379 test-archive-1701ef1f1510/foo
379 test-archive-1701ef1f1510/foo
380
380
381 test that we detect file patterns that match no files
381 test that we detect file patterns that match no files
382
382
383 $ "$PYTHON" getarchive.py "$TIP" gz foobar
383 $ "$PYTHON" getarchive.py "$TIP" gz foobar
384 HTTP Error 404: file(s) not found: foobar
384 HTTP Error 404: file(s) not found: foobar
385
385
386 test that we reject unsafe patterns
386 test that we reject unsafe patterns
387
387
388 $ "$PYTHON" getarchive.py "$TIP" gz relre:baz
388 $ "$PYTHON" getarchive.py "$TIP" gz relre:baz
389 HTTP Error 404: file(s) not found: relre:baz
389 HTTP Error 404: file(s) not found: relre:baz
390
390
391 $ killdaemons.py
391 $ killdaemons.py
392
392
393 $ hg archive -t tar test.tar
393 $ hg archive -t tar test.tar
394 $ tar tf test.tar
394 $ tar tf test.tar
395 test/.hg_archival.txt
395 test/.hg_archival.txt
396 test/.hgsub
396 test/.hgsub
397 test/.hgsubstate
397 test/.hgsubstate
398 test/bar
398 test/bar
399 test/baz/bletch
399 test/baz/bletch
400 test/foo
400 test/foo
401
401
402 $ hg archive --debug -t tbz2 -X baz test.tar.bz2 --config progress.debug=true
402 $ hg archive --debug -t tbz2 -X baz test.tar.bz2 --config progress.debug=true
403 archiving: 0/4 files (0.00%)
403 archiving: 0/4 files (0.00%)
404 archiving: .hgsub 1/4 files (25.00%)
404 archiving: .hgsub 1/4 files (25.00%)
405 archiving: .hgsubstate 2/4 files (50.00%)
405 archiving: .hgsubstate 2/4 files (50.00%)
406 archiving: bar 3/4 files (75.00%)
406 archiving: bar 3/4 files (75.00%)
407 archiving: foo 4/4 files (100.00%)
407 archiving: foo 4/4 files (100.00%)
408 $ bunzip2 -dc test.tar.bz2 | tar tf - 2>/dev/null
408 $ bunzip2 -dc test.tar.bz2 | tar tf - 2>/dev/null
409 test/.hg_archival.txt
409 test/.hg_archival.txt
410 test/.hgsub
410 test/.hgsub
411 test/.hgsubstate
411 test/.hgsubstate
412 test/bar
412 test/bar
413 test/foo
413 test/foo
414
414
415 $ hg archive -t tgz -p %b-%h test-%h.tar.gz
415 $ hg archive -t tgz -p %b-%h test-%h.tar.gz
416 $ gzip -dc test-$QTIP.tar.gz | tar tf - 2>/dev/null
416 $ gzip -dc test-$QTIP.tar.gz | tar tf - 2>/dev/null
417 test-1701ef1f1510/.hg_archival.txt
417 test-1701ef1f1510/.hg_archival.txt
418 test-1701ef1f1510/.hgsub
418 test-1701ef1f1510/.hgsub
419 test-1701ef1f1510/.hgsubstate
419 test-1701ef1f1510/.hgsubstate
420 test-1701ef1f1510/bar
420 test-1701ef1f1510/bar
421 test-1701ef1f1510/baz/bletch
421 test-1701ef1f1510/baz/bletch
422 test-1701ef1f1510/foo
422 test-1701ef1f1510/foo
423
423
424 $ hg archive autodetected_test.tar
424 $ hg archive autodetected_test.tar
425 $ tar tf autodetected_test.tar
425 $ tar tf autodetected_test.tar
426 autodetected_test/.hg_archival.txt
426 autodetected_test/.hg_archival.txt
427 autodetected_test/.hgsub
427 autodetected_test/.hgsub
428 autodetected_test/.hgsubstate
428 autodetected_test/.hgsubstate
429 autodetected_test/bar
429 autodetected_test/bar
430 autodetected_test/baz/bletch
430 autodetected_test/baz/bletch
431 autodetected_test/foo
431 autodetected_test/foo
432
432
433 The '-t' should override autodetection
433 The '-t' should override autodetection
434
434
435 $ hg archive -t tar autodetect_override_test.zip
435 $ hg archive -t tar autodetect_override_test.zip
436 $ tar tf autodetect_override_test.zip
436 $ tar tf autodetect_override_test.zip
437 autodetect_override_test.zip/.hg_archival.txt
437 autodetect_override_test.zip/.hg_archival.txt
438 autodetect_override_test.zip/.hgsub
438 autodetect_override_test.zip/.hgsub
439 autodetect_override_test.zip/.hgsubstate
439 autodetect_override_test.zip/.hgsubstate
440 autodetect_override_test.zip/bar
440 autodetect_override_test.zip/bar
441 autodetect_override_test.zip/baz/bletch
441 autodetect_override_test.zip/baz/bletch
442 autodetect_override_test.zip/foo
442 autodetect_override_test.zip/foo
443
443
444 $ for ext in tar tar.gz tgz tar.bz2 tbz2 zip; do
444 $ for ext in tar tar.gz tgz tar.bz2 tbz2 zip; do
445 > hg archive auto_test.$ext
445 > hg archive auto_test.$ext
446 > if [ -d auto_test.$ext ]; then
446 > if [ -d auto_test.$ext ]; then
447 > echo "extension $ext was not autodetected."
447 > echo "extension $ext was not autodetected."
448 > fi
448 > fi
449 > done
449 > done
450
450
451 $ cat > md5comp.py <<EOF
451 $ cat > md5comp.py <<EOF
452 > from __future__ import absolute_import, print_function
452 > from __future__ import absolute_import, print_function
453 > import hashlib
453 > import hashlib
454 > import sys
454 > import sys
455 > f1, f2 = sys.argv[1:3]
455 > f1, f2 = sys.argv[1:3]
456 > h1 = hashlib.md5(open(f1, 'rb').read()).hexdigest()
456 > h1 = hashlib.md5(open(f1, 'rb').read()).hexdigest()
457 > h2 = hashlib.md5(open(f2, 'rb').read()).hexdigest()
457 > h2 = hashlib.md5(open(f2, 'rb').read()).hexdigest()
458 > print(h1 == h2 or "md5 differ: " + repr((h1, h2)))
458 > print(h1 == h2 or "md5 differ: " + repr((h1, h2)))
459 > EOF
459 > EOF
460
460
461 archive name is stored in the archive, so create similar archives and
461 archive name is stored in the archive, so create similar archives and
462 rename them afterwards.
462 rename them afterwards.
463
463
464 $ hg archive -t tgz tip.tar.gz
464 $ hg archive -t tgz tip.tar.gz
465 $ mv tip.tar.gz tip1.tar.gz
465 $ mv tip.tar.gz tip1.tar.gz
466 $ sleep 1
466 $ sleep 1
467 $ hg archive -t tgz tip.tar.gz
467 $ hg archive -t tgz tip.tar.gz
468 $ mv tip.tar.gz tip2.tar.gz
468 $ mv tip.tar.gz tip2.tar.gz
469 $ "$PYTHON" md5comp.py tip1.tar.gz tip2.tar.gz
469 $ "$PYTHON" md5comp.py tip1.tar.gz tip2.tar.gz
470 True
470 True
471
471
472 $ hg archive -t zip -p /illegal test.zip
472 $ hg archive -t zip -p /illegal test.zip
473 abort: archive prefix contains illegal components
473 abort: archive prefix contains illegal components
474 [255]
474 [255]
475 $ hg archive -t zip -p very/../bad test.zip
475 $ hg archive -t zip -p very/../bad test.zip
476
476
477 $ hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
477 $ hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
478 $ unzip -t test.zip
478 $ unzip -t test.zip
479 Archive: test.zip
479 Archive: test.zip
480 testing: test/bar*OK (glob)
480 testing: test/bar*OK (glob)
481 testing: test/baz/bletch*OK (glob)
481 testing: test/baz/bletch*OK (glob)
482 testing: test/foo*OK (glob)
482 testing: test/foo*OK (glob)
483 No errors detected in compressed data of test.zip.
483 No errors detected in compressed data of test.zip.
484
484
485 $ hg archive -t tar - | tar tf - 2>/dev/null
485 $ hg archive -t tar - | tar tf - 2>/dev/null
486 test-1701ef1f1510/.hg_archival.txt
486 test-1701ef1f1510/.hg_archival.txt
487 test-1701ef1f1510/.hgsub
487 test-1701ef1f1510/.hgsub
488 test-1701ef1f1510/.hgsubstate
488 test-1701ef1f1510/.hgsubstate
489 test-1701ef1f1510/bar
489 test-1701ef1f1510/bar
490 test-1701ef1f1510/baz/bletch
490 test-1701ef1f1510/baz/bletch
491 test-1701ef1f1510/foo
491 test-1701ef1f1510/foo
492
492
493 $ hg archive -r 0 -t tar rev-%r.tar
493 $ hg archive -r 0 -t tar rev-%r.tar
494 $ [ -f rev-0.tar ]
494 $ [ -f rev-0.tar ]
495
495
496 test .hg_archival.txt
496 test .hg_archival.txt
497
497
498 $ hg archive ../test-tags
498 $ hg archive ../test-tags
499 $ cat ../test-tags/.hg_archival.txt
499 $ cat ../test-tags/.hg_archival.txt
500 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
500 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
501 node: 1701ef1f151069b8747038e93b5186bb43a47504
501 node: 1701ef1f151069b8747038e93b5186bb43a47504
502 branch: default
502 branch: default
503 latesttag: null
503 latesttag: null
504 latesttagdistance: 4
504 latesttagdistance: 4
505 changessincelatesttag: 4
505 changessincelatesttag: 4
506 $ hg tag -r 2 mytag
506 $ hg tag -r 2 mytag
507 $ hg tag -r 2 anothertag
507 $ hg tag -r 2 anothertag
508 $ hg archive -r 2 ../test-lasttag
508 $ hg archive -r 2 ../test-lasttag
509 $ cat ../test-lasttag/.hg_archival.txt
509 $ cat ../test-lasttag/.hg_archival.txt
510 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
510 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
511 node: 2c0277f05ed49d1c8328fb9ba92fba7a5ebcb33e
511 node: 2c0277f05ed49d1c8328fb9ba92fba7a5ebcb33e
512 branch: default
512 branch: default
513 tag: anothertag
513 tag: anothertag
514 tag: mytag
514 tag: mytag
515
515
516 $ hg archive -t bogus test.bogus
516 $ hg archive -t bogus test.bogus
517 abort: unknown archive type 'bogus'
517 abort: unknown archive type 'bogus'
518 [255]
518 [255]
519
519
520 enable progress extension:
520 enable progress extension:
521
521
522 $ cp $HGRCPATH $HGRCPATH.no-progress
522 $ cp $HGRCPATH $HGRCPATH.no-progress
523 $ cat >> $HGRCPATH <<EOF
523 $ cat >> $HGRCPATH <<EOF
524 > [progress]
524 > [progress]
525 > assume-tty = 1
525 > assume-tty = 1
526 > format = topic bar number
526 > format = topic bar number
527 > delay = 0
527 > delay = 0
528 > refresh = 0
528 > refresh = 0
529 > width = 60
529 > width = 60
530 > EOF
530 > EOF
531
531
532 $ hg archive ../with-progress
532 $ hg archive ../with-progress
533 \r (no-eol) (esc)
533 \r (no-eol) (esc)
534 archiving [ ] 0/6\r (no-eol) (esc)
534 archiving [ ] 0/6\r (no-eol) (esc)
535 archiving [======> ] 1/6\r (no-eol) (esc)
535 archiving [======> ] 1/6\r (no-eol) (esc)
536 archiving [=============> ] 2/6\r (no-eol) (esc)
536 archiving [=============> ] 2/6\r (no-eol) (esc)
537 archiving [====================> ] 3/6\r (no-eol) (esc)
537 archiving [====================> ] 3/6\r (no-eol) (esc)
538 archiving [===========================> ] 4/6\r (no-eol) (esc)
538 archiving [===========================> ] 4/6\r (no-eol) (esc)
539 archiving [==================================> ] 5/6\r (no-eol) (esc)
539 archiving [==================================> ] 5/6\r (no-eol) (esc)
540 archiving [==========================================>] 6/6\r (no-eol) (esc)
540 archiving [==========================================>] 6/6\r (no-eol) (esc)
541 \r (no-eol) (esc)
541 \r (no-eol) (esc)
542
542
543 cleanup after progress extension test:
543 cleanup after progress extension test:
544
544
545 $ cp $HGRCPATH.no-progress $HGRCPATH
545 $ cp $HGRCPATH.no-progress $HGRCPATH
546
546
547 server errors
547 server errors
548
548
549 $ cat errors.log
549 $ cat errors.log
550
550
551 empty repo
551 empty repo
552
552
553 $ hg init ../empty
553 $ hg init ../empty
554 $ cd ../empty
554 $ cd ../empty
555 $ hg archive ../test-empty
555 $ hg archive ../test-empty
556 abort: no working directory: please specify a revision
556 abort: no working directory: please specify a revision
557 [255]
557 [255]
558
558
559 old file -- date clamped to 1980
559 old file -- date clamped to 1980
560
560
561 $ touch -t 197501010000 old
561 $ touch -t 197501010000 old
562 $ hg add old
562 $ hg add old
563 $ hg commit -m old
563 $ hg commit -m old
564 $ hg archive ../old.zip
564 $ hg archive ../old.zip
565 $ unzip -l ../old.zip | grep -v -- ----- | egrep -v files$
565 $ unzip -l ../old.zip | grep -v -- ----- | egrep -v files$
566 Archive: ../old.zip
566 Archive: ../old.zip
567 \s*Length.* (re)
567 \s*Length.* (re)
568 *172*80*00:00*old/.hg_archival.txt (glob)
568 *172*80*00:00*old/.hg_archival.txt (glob)
569 *0*80*00:00*old/old (glob)
569 *0*80*00:00*old/old (glob)
570
570
571 show an error when a provided pattern matches no files
571 show an error when a provided pattern matches no files
572
572
573 $ hg archive -I file_that_does_not_exist.foo ../empty.zip
573 $ hg archive -I file_that_does_not_exist.foo ../empty.zip
574 abort: no files match the archive pattern
574 abort: no files match the archive pattern
575 [255]
575 [255]
576
576
577 $ hg archive -X * ../empty.zip
577 $ hg archive -X * ../empty.zip
578 abort: no files match the archive pattern
578 abort: no files match the archive pattern
579 [255]
579 [255]
580
580
581 $ cd ..
581 $ cd ..
582
582
583 issue3600: check whether "hg archive" can create archive files which
583 issue3600: check whether "hg archive" can create archive files which
584 are extracted with expected timestamp, even though TZ is not
584 are extracted with expected timestamp, even though TZ is not
585 configured as GMT.
585 configured as GMT.
586
586
587 $ mkdir issue3600
587 $ mkdir issue3600
588 $ cd issue3600
588 $ cd issue3600
589
589
590 $ hg init repo
590 $ hg init repo
591 $ echo a > repo/a
591 $ echo a > repo/a
592 $ hg -R repo add repo/a
592 $ hg -R repo add repo/a
593 $ hg -R repo commit -m '#0' -d '456789012 21600'
593 $ hg -R repo commit -m '#0' -d '456789012 21600'
594 $ cat > show_mtime.py <<EOF
594 $ cat > show_mtime.py <<EOF
595 > from __future__ import absolute_import, print_function
595 > from __future__ import absolute_import, print_function
596 > import os
596 > import os
597 > import sys
597 > import sys
598 > print(int(os.stat(sys.argv[1]).st_mtime))
598 > print(int(os.stat(sys.argv[1]).st_mtime))
599 > EOF
599 > EOF
600
600
601 $ hg -R repo archive --prefix tar-extracted archive.tar
601 $ hg -R repo archive --prefix tar-extracted archive.tar
602 $ (TZ=UTC-3; export TZ; tar xf archive.tar)
602 $ (TZ=UTC-3; export TZ; tar xf archive.tar)
603 $ "$PYTHON" show_mtime.py tar-extracted/a
603 $ "$PYTHON" show_mtime.py tar-extracted/a
604 456789012
604 456789012
605
605
606 $ hg -R repo archive --prefix zip-extracted archive.zip
606 $ hg -R repo archive --prefix zip-extracted archive.zip
607 $ (TZ=UTC-3; export TZ; unzip -q archive.zip)
607 $ (TZ=UTC-3; export TZ; unzip -q archive.zip)
608 $ "$PYTHON" show_mtime.py zip-extracted/a
608 $ "$PYTHON" show_mtime.py zip-extracted/a
609 456789012
609 456789012
610
610
611 $ cd ..
611 $ cd ..
@@ -1,151 +1,151 b''
1 ensure that failing ui.atexit handlers report sensibly
1 ensure that failing ui.atexit handlers report sensibly
2
2
3 $ cat > $TESTTMP/bailatexit.py <<EOF
3 $ cat > $TESTTMP/bailatexit.py <<EOF
4 > from mercurial import util
4 > from mercurial import util
5 > def bail():
5 > def bail():
6 > raise RuntimeError('ui.atexit handler exception')
6 > raise RuntimeError('ui.atexit handler exception')
7 >
7 >
8 > def extsetup(ui):
8 > def extsetup(ui):
9 > ui.atexit(bail)
9 > ui.atexit(bail)
10 > EOF
10 > EOF
11 $ hg -q --config extensions.bailatexit=$TESTTMP/bailatexit.py \
11 $ hg -q --config extensions.bailatexit=$TESTTMP/bailatexit.py \
12 > help help
12 > help help
13 hg help [-ecks] [TOPIC]
13 hg help [-ecks] [TOPIC]
14
14
15 show help for a given topic or a help overview
15 show help for a given topic or a help overview
16 error in exit handlers:
16 error in exit handlers:
17 Traceback (most recent call last):
17 Traceback (most recent call last):
18 File "*/mercurial/dispatch.py", line *, in _runexithandlers (glob)
18 File "*/mercurial/dispatch.py", line *, in _runexithandlers (glob)
19 func(*args, **kwargs)
19 func(*args, **kwargs)
20 File "$TESTTMP/bailatexit.py", line *, in bail (glob)
20 File "$TESTTMP/bailatexit.py", line *, in bail (glob)
21 raise RuntimeError('ui.atexit handler exception')
21 raise RuntimeError('ui.atexit handler exception')
22 RuntimeError: ui.atexit handler exception
22 RuntimeError: ui.atexit handler exception
23 [255]
23 [255]
24
24
25 $ rm $TESTTMP/bailatexit.py
25 $ rm $TESTTMP/bailatexit.py
26
26
27 another bad extension
27 another bad extension
28
28
29 $ echo 'raise Exception("bit bucket overflow")' > badext.py
29 $ echo 'raise Exception("bit bucket overflow")' > badext.py
30 $ abspathexc=`pwd`/badext.py
30 $ abspathexc=`pwd`/badext.py
31
31
32 $ cat >baddocext.py <<EOF
32 $ cat >baddocext.py <<EOF
33 > """
33 > """
34 > baddocext is bad
34 > baddocext is bad
35 > """
35 > """
36 > EOF
36 > EOF
37 $ abspathdoc=`pwd`/baddocext.py
37 $ abspathdoc=`pwd`/baddocext.py
38
38
39 $ cat <<EOF >> $HGRCPATH
39 $ cat <<EOF >> $HGRCPATH
40 > [extensions]
40 > [extensions]
41 > gpg =
41 > gpg =
42 > hgext.gpg =
42 > hgext.gpg =
43 > badext = $abspathexc
43 > badext = $abspathexc
44 > baddocext = $abspathdoc
44 > baddocext = $abspathdoc
45 > badext2 =
45 > badext2 =
46 > EOF
46 > EOF
47
47
48 $ hg -q help help 2>&1 |grep extension
48 $ hg -q help help 2>&1 |grep extension
49 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
49 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
50 *** failed to import extension badext2: No module named *badext2* (glob)
50 *** failed to import extension badext2: No module named *badext2* (glob)
51
51
52 show traceback
52 show traceback
53
53
54 $ hg -q help help --traceback 2>&1 | egrep ' extension|^Exception|Traceback|ImportError|ModuleNotFound'
54 $ hg -q help help --traceback 2>&1 | egrep ' extension|^Exception|Traceback|ImportError|ModuleNotFound'
55 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
55 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
56 Traceback (most recent call last):
56 Traceback (most recent call last):
57 Exception: bit bucket overflow
57 Exception: bit bucket overflow
58 *** failed to import extension badext2: No module named *badext2* (glob)
58 *** failed to import extension badext2: No module named *badext2* (glob)
59 Traceback (most recent call last):
59 Traceback (most recent call last):
60 ImportError: No module named badext2 (no-py3k !)
60 ImportError: No module named badext2 (no-py3 !)
61 ModuleNotFoundError: No module named 'hgext.badext2' (py3k !)
61 ModuleNotFoundError: No module named 'hgext.badext2' (py3 !)
62 Traceback (most recent call last): (py3k !)
62 Traceback (most recent call last): (py3 !)
63 ModuleNotFoundError: No module named 'hgext3rd.badext2' (py3k !)
63 ModuleNotFoundError: No module named 'hgext3rd.badext2' (py3 !)
64 Traceback (most recent call last): (py3k !)
64 Traceback (most recent call last): (py3 !)
65 ModuleNotFoundError: No module named 'badext2' (py3k !)
65 ModuleNotFoundError: No module named 'badext2' (py3 !)
66
66
67 names of extensions failed to load can be accessed via extensions.notloaded()
67 names of extensions failed to load can be accessed via extensions.notloaded()
68
68
69 $ cat <<EOF > showbadexts.py
69 $ cat <<EOF > showbadexts.py
70 > from mercurial import commands, extensions, registrar
70 > from mercurial import commands, extensions, registrar
71 > cmdtable = {}
71 > cmdtable = {}
72 > command = registrar.command(cmdtable)
72 > command = registrar.command(cmdtable)
73 > @command(b'showbadexts', norepo=True)
73 > @command(b'showbadexts', norepo=True)
74 > def showbadexts(ui, *pats, **opts):
74 > def showbadexts(ui, *pats, **opts):
75 > ui.write(b'BADEXTS: %s\n' % b' '.join(sorted(extensions.notloaded())))
75 > ui.write(b'BADEXTS: %s\n' % b' '.join(sorted(extensions.notloaded())))
76 > EOF
76 > EOF
77 $ hg --config extensions.badexts=showbadexts.py showbadexts 2>&1 | grep '^BADEXTS'
77 $ hg --config extensions.badexts=showbadexts.py showbadexts 2>&1 | grep '^BADEXTS'
78 BADEXTS: badext badext2
78 BADEXTS: badext badext2
79
79
80 #if no-extraextensions
80 #if no-extraextensions
81 show traceback for ImportError of hgext.name if devel.debug.extensions is set
81 show traceback for ImportError of hgext.name if devel.debug.extensions is set
82
82
83 $ (hg help help --traceback --debug --config devel.debug.extensions=yes 2>&1) \
83 $ (hg help help --traceback --debug --config devel.debug.extensions=yes 2>&1) \
84 > | grep -v '^ ' \
84 > | grep -v '^ ' \
85 > | egrep 'extension..[^p]|^Exception|Traceback|ImportError|not import|ModuleNotFound'
85 > | egrep 'extension..[^p]|^Exception|Traceback|ImportError|not import|ModuleNotFound'
86 debug.extensions: loading extensions
86 debug.extensions: loading extensions
87 debug.extensions: - processing 5 entries
87 debug.extensions: - processing 5 entries
88 debug.extensions: - loading extension: 'gpg'
88 debug.extensions: - loading extension: 'gpg'
89 debug.extensions: > 'gpg' extension loaded in * (glob)
89 debug.extensions: > 'gpg' extension loaded in * (glob)
90 debug.extensions: - validating extension tables: 'gpg'
90 debug.extensions: - validating extension tables: 'gpg'
91 debug.extensions: - invoking registered callbacks: 'gpg'
91 debug.extensions: - invoking registered callbacks: 'gpg'
92 debug.extensions: > callbacks completed in * (glob)
92 debug.extensions: > callbacks completed in * (glob)
93 debug.extensions: - loading extension: 'badext'
93 debug.extensions: - loading extension: 'badext'
94 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
94 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
95 Traceback (most recent call last):
95 Traceback (most recent call last):
96 Exception: bit bucket overflow
96 Exception: bit bucket overflow
97 debug.extensions: - loading extension: 'baddocext'
97 debug.extensions: - loading extension: 'baddocext'
98 debug.extensions: > 'baddocext' extension loaded in * (glob)
98 debug.extensions: > 'baddocext' extension loaded in * (glob)
99 debug.extensions: - validating extension tables: 'baddocext'
99 debug.extensions: - validating extension tables: 'baddocext'
100 debug.extensions: - invoking registered callbacks: 'baddocext'
100 debug.extensions: - invoking registered callbacks: 'baddocext'
101 debug.extensions: > callbacks completed in * (glob)
101 debug.extensions: > callbacks completed in * (glob)
102 debug.extensions: - loading extension: 'badext2'
102 debug.extensions: - loading extension: 'badext2'
103 debug.extensions: - could not import hgext.badext2 (No module named *badext2*): trying hgext3rd.badext2 (glob)
103 debug.extensions: - could not import hgext.badext2 (No module named *badext2*): trying hgext3rd.badext2 (glob)
104 Traceback (most recent call last):
104 Traceback (most recent call last):
105 ImportError: No module named badext2 (no-py3k !)
105 ImportError: No module named badext2 (no-py3 !)
106 ModuleNotFoundError: No module named 'hgext.badext2' (py3k !)
106 ModuleNotFoundError: No module named 'hgext.badext2' (py3 !)
107 debug.extensions: - could not import hgext3rd.badext2 (No module named *badext2*): trying badext2 (glob)
107 debug.extensions: - could not import hgext3rd.badext2 (No module named *badext2*): trying badext2 (glob)
108 Traceback (most recent call last):
108 Traceback (most recent call last):
109 ImportError: No module named badext2 (no-py3k !)
109 ImportError: No module named badext2 (no-py3 !)
110 ModuleNotFoundError: No module named 'hgext.badext2' (py3k !)
110 ModuleNotFoundError: No module named 'hgext.badext2' (py3 !)
111 Traceback (most recent call last): (py3k !)
111 Traceback (most recent call last): (py3 !)
112 ModuleNotFoundError: No module named 'hgext3rd.badext2' (py3k !)
112 ModuleNotFoundError: No module named 'hgext3rd.badext2' (py3 !)
113 *** failed to import extension badext2: No module named *badext2* (glob)
113 *** failed to import extension badext2: No module named *badext2* (glob)
114 Traceback (most recent call last):
114 Traceback (most recent call last):
115 ModuleNotFoundError: No module named 'hgext.badext2' (py3k !)
115 ModuleNotFoundError: No module named 'hgext.badext2' (py3 !)
116 Traceback (most recent call last): (py3k !)
116 Traceback (most recent call last): (py3 !)
117 ModuleNotFoundError: No module named 'hgext3rd.badext2' (py3k !)
117 ModuleNotFoundError: No module named 'hgext3rd.badext2' (py3 !)
118 Traceback (most recent call last): (py3k !)
118 Traceback (most recent call last): (py3 !)
119 ModuleNotFoundError: No module named 'badext2' (py3k !)
119 ModuleNotFoundError: No module named 'badext2' (py3 !)
120 ImportError: No module named badext2 (no-py3k !)
120 ImportError: No module named badext2 (no-py3 !)
121 debug.extensions: > loaded 2 extensions, total time * (glob)
121 debug.extensions: > loaded 2 extensions, total time * (glob)
122 debug.extensions: - loading configtable attributes
122 debug.extensions: - loading configtable attributes
123 debug.extensions: - executing uisetup hooks
123 debug.extensions: - executing uisetup hooks
124 debug.extensions: - running uisetup for 'gpg'
124 debug.extensions: - running uisetup for 'gpg'
125 debug.extensions: > uisetup for 'gpg' took * (glob)
125 debug.extensions: > uisetup for 'gpg' took * (glob)
126 debug.extensions: - running uisetup for 'baddocext'
126 debug.extensions: - running uisetup for 'baddocext'
127 debug.extensions: > uisetup for 'baddocext' took * (glob)
127 debug.extensions: > uisetup for 'baddocext' took * (glob)
128 debug.extensions: > all uisetup took * (glob)
128 debug.extensions: > all uisetup took * (glob)
129 debug.extensions: - executing extsetup hooks
129 debug.extensions: - executing extsetup hooks
130 debug.extensions: - running extsetup for 'gpg'
130 debug.extensions: - running extsetup for 'gpg'
131 debug.extensions: > extsetup for 'gpg' took * (glob)
131 debug.extensions: > extsetup for 'gpg' took * (glob)
132 debug.extensions: - running extsetup for 'baddocext'
132 debug.extensions: - running extsetup for 'baddocext'
133 debug.extensions: > extsetup for 'baddocext' took * (glob)
133 debug.extensions: > extsetup for 'baddocext' took * (glob)
134 debug.extensions: > all extsetup took * (glob)
134 debug.extensions: > all extsetup took * (glob)
135 debug.extensions: - executing remaining aftercallbacks
135 debug.extensions: - executing remaining aftercallbacks
136 debug.extensions: > remaining aftercallbacks completed in * (glob)
136 debug.extensions: > remaining aftercallbacks completed in * (glob)
137 debug.extensions: - loading extension registration objects
137 debug.extensions: - loading extension registration objects
138 debug.extensions: > extension registration object loading took * (glob)
138 debug.extensions: > extension registration object loading took * (glob)
139 debug.extensions: > extension baddocext take a total of * to load (glob)
139 debug.extensions: > extension baddocext take a total of * to load (glob)
140 debug.extensions: > extension gpg take a total of * to load (glob)
140 debug.extensions: > extension gpg take a total of * to load (glob)
141 debug.extensions: extension loading complete
141 debug.extensions: extension loading complete
142 #endif
142 #endif
143
143
144 confirm that there's no crash when an extension's documentation is bad
144 confirm that there's no crash when an extension's documentation is bad
145
145
146 $ hg help --keyword baddocext
146 $ hg help --keyword baddocext
147 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
147 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
148 *** failed to import extension badext2: No module named *badext2* (glob)
148 *** failed to import extension badext2: No module named *badext2* (glob)
149 Topics:
149 Topics:
150
150
151 extensions Using Additional Features
151 extensions Using Additional Features
@@ -1,48 +1,48 b''
1 #require test-repo
1 #require test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4 $ cd "$TESTDIR"/..
4 $ cd "$TESTDIR"/..
5
5
6 #if no-py3k
6 #if no-py3
7 $ testrepohg files 'set:(**.py)' \
7 $ testrepohg files 'set:(**.py)' \
8 > -X hgdemandimport/demandimportpy2.py \
8 > -X hgdemandimport/demandimportpy2.py \
9 > -X mercurial/thirdparty/cbor \
9 > -X mercurial/thirdparty/cbor \
10 > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py
10 > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py
11 contrib/python-zstandard/setup.py not using absolute_import
11 contrib/python-zstandard/setup.py not using absolute_import
12 contrib/python-zstandard/setup_zstd.py not using absolute_import
12 contrib/python-zstandard/setup_zstd.py not using absolute_import
13 contrib/python-zstandard/tests/common.py not using absolute_import
13 contrib/python-zstandard/tests/common.py not using absolute_import
14 contrib/python-zstandard/tests/test_buffer_util.py not using absolute_import
14 contrib/python-zstandard/tests/test_buffer_util.py not using absolute_import
15 contrib/python-zstandard/tests/test_compressor.py not using absolute_import
15 contrib/python-zstandard/tests/test_compressor.py not using absolute_import
16 contrib/python-zstandard/tests/test_compressor_fuzzing.py not using absolute_import
16 contrib/python-zstandard/tests/test_compressor_fuzzing.py not using absolute_import
17 contrib/python-zstandard/tests/test_data_structures.py not using absolute_import
17 contrib/python-zstandard/tests/test_data_structures.py not using absolute_import
18 contrib/python-zstandard/tests/test_data_structures_fuzzing.py not using absolute_import
18 contrib/python-zstandard/tests/test_data_structures_fuzzing.py not using absolute_import
19 contrib/python-zstandard/tests/test_decompressor.py not using absolute_import
19 contrib/python-zstandard/tests/test_decompressor.py not using absolute_import
20 contrib/python-zstandard/tests/test_decompressor_fuzzing.py not using absolute_import
20 contrib/python-zstandard/tests/test_decompressor_fuzzing.py not using absolute_import
21 contrib/python-zstandard/tests/test_estimate_sizes.py not using absolute_import
21 contrib/python-zstandard/tests/test_estimate_sizes.py not using absolute_import
22 contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import
22 contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import
23 contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import
23 contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import
24 setup.py not using absolute_import
24 setup.py not using absolute_import
25 #endif
25 #endif
26
26
27 #if py3k
27 #if py3
28 $ testrepohg files 'set:(**.py) - grep(pygments)' \
28 $ testrepohg files 'set:(**.py) - grep(pygments)' \
29 > -X hgdemandimport/demandimportpy2.py \
29 > -X hgdemandimport/demandimportpy2.py \
30 > -X hgext/fsmonitor/pywatchman \
30 > -X hgext/fsmonitor/pywatchman \
31 > -X mercurial/cffi \
31 > -X mercurial/cffi \
32 > -X mercurial/thirdparty \
32 > -X mercurial/thirdparty \
33 > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py \
33 > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py \
34 > | sed 's/[0-9][0-9]*)$/*)/'
34 > | sed 's/[0-9][0-9]*)$/*)/'
35 hgext/convert/transport.py: error importing: <*Error> No module named 'svn.client' (error at transport.py:*) (glob) (?)
35 hgext/convert/transport.py: error importing: <*Error> No module named 'svn.client' (error at transport.py:*) (glob) (?)
36 hgext/infinitepush/sqlindexapi.py: error importing: <*Error> No module named 'mysql' (error at sqlindexapi.py:*) (glob) (?)
36 hgext/infinitepush/sqlindexapi.py: error importing: <*Error> No module named 'mysql' (error at sqlindexapi.py:*) (glob) (?)
37 mercurial/scmwindows.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !)
37 mercurial/scmwindows.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !)
38 mercurial/win32.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !)
38 mercurial/win32.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !)
39 mercurial/windows.py: error importing: <ModuleNotFoundError> No module named 'msvcrt' (error at windows.py:*) (no-windows !)
39 mercurial/windows.py: error importing: <ModuleNotFoundError> No module named 'msvcrt' (error at windows.py:*) (no-windows !)
40 mercurial/posix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at posix.py:*) (windows !)
40 mercurial/posix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at posix.py:*) (windows !)
41 mercurial/scmposix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at scmposix.py:*) (windows !)
41 mercurial/scmposix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at scmposix.py:*) (windows !)
42 #endif
42 #endif
43
43
44 #if py3k pygments
44 #if py3 pygments
45 $ testrepohg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \
45 $ testrepohg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \
46 > | xargs "$PYTHON" contrib/check-py3-compat.py \
46 > | xargs "$PYTHON" contrib/check-py3-compat.py \
47 > | sed 's/[0-9][0-9]*)$/*)/'
47 > | sed 's/[0-9][0-9]*)$/*)/'
48 #endif
48 #endif
@@ -1,635 +1,635 b''
1 $ cat << EOF >> $HGRCPATH
1 $ cat << EOF >> $HGRCPATH
2 > [ui]
2 > [ui]
3 > interactive=yes
3 > interactive=yes
4 > EOF
4 > EOF
5
5
6 $ hg init debugrevlog
6 $ hg init debugrevlog
7 $ cd debugrevlog
7 $ cd debugrevlog
8 $ echo a > a
8 $ echo a > a
9 $ hg ci -Am adda
9 $ hg ci -Am adda
10 adding a
10 adding a
11 $ hg rm .
11 $ hg rm .
12 removing a
12 removing a
13 $ hg ci -Am make-it-empty
13 $ hg ci -Am make-it-empty
14 $ hg revert --all -r 0
14 $ hg revert --all -r 0
15 adding a
15 adding a
16 $ hg ci -Am make-it-full
16 $ hg ci -Am make-it-full
17 #if reporevlogstore
17 #if reporevlogstore
18 $ hg debugrevlog -c
18 $ hg debugrevlog -c
19 format : 1
19 format : 1
20 flags : inline
20 flags : inline
21
21
22 revisions : 3
22 revisions : 3
23 merges : 0 ( 0.00%)
23 merges : 0 ( 0.00%)
24 normal : 3 (100.00%)
24 normal : 3 (100.00%)
25 revisions : 3
25 revisions : 3
26 empty : 0 ( 0.00%)
26 empty : 0 ( 0.00%)
27 text : 0 (100.00%)
27 text : 0 (100.00%)
28 delta : 0 (100.00%)
28 delta : 0 (100.00%)
29 snapshot : 3 (100.00%)
29 snapshot : 3 (100.00%)
30 lvl-0 : 3 (100.00%)
30 lvl-0 : 3 (100.00%)
31 deltas : 0 ( 0.00%)
31 deltas : 0 ( 0.00%)
32 revision size : 191
32 revision size : 191
33 snapshot : 191 (100.00%)
33 snapshot : 191 (100.00%)
34 lvl-0 : 191 (100.00%)
34 lvl-0 : 191 (100.00%)
35 deltas : 0 ( 0.00%)
35 deltas : 0 ( 0.00%)
36
36
37 chunks : 3
37 chunks : 3
38 0x75 (u) : 3 (100.00%)
38 0x75 (u) : 3 (100.00%)
39 chunks size : 191
39 chunks size : 191
40 0x75 (u) : 191 (100.00%)
40 0x75 (u) : 191 (100.00%)
41
41
42 avg chain length : 0
42 avg chain length : 0
43 max chain length : 0
43 max chain length : 0
44 max chain reach : 67
44 max chain reach : 67
45 compression ratio : 0
45 compression ratio : 0
46
46
47 uncompressed data size (min/max/avg) : 57 / 66 / 62
47 uncompressed data size (min/max/avg) : 57 / 66 / 62
48 full revision size (min/max/avg) : 58 / 67 / 63
48 full revision size (min/max/avg) : 58 / 67 / 63
49 inter-snapshot size (min/max/avg) : 0 / 0 / 0
49 inter-snapshot size (min/max/avg) : 0 / 0 / 0
50 delta size (min/max/avg) : 0 / 0 / 0
50 delta size (min/max/avg) : 0 / 0 / 0
51 $ hg debugrevlog -m
51 $ hg debugrevlog -m
52 format : 1
52 format : 1
53 flags : inline, generaldelta
53 flags : inline, generaldelta
54
54
55 revisions : 3
55 revisions : 3
56 merges : 0 ( 0.00%)
56 merges : 0 ( 0.00%)
57 normal : 3 (100.00%)
57 normal : 3 (100.00%)
58 revisions : 3
58 revisions : 3
59 empty : 1 (33.33%)
59 empty : 1 (33.33%)
60 text : 1 (100.00%)
60 text : 1 (100.00%)
61 delta : 0 ( 0.00%)
61 delta : 0 ( 0.00%)
62 snapshot : 2 (66.67%)
62 snapshot : 2 (66.67%)
63 lvl-0 : 2 (66.67%)
63 lvl-0 : 2 (66.67%)
64 deltas : 0 ( 0.00%)
64 deltas : 0 ( 0.00%)
65 revision size : 88
65 revision size : 88
66 snapshot : 88 (100.00%)
66 snapshot : 88 (100.00%)
67 lvl-0 : 88 (100.00%)
67 lvl-0 : 88 (100.00%)
68 deltas : 0 ( 0.00%)
68 deltas : 0 ( 0.00%)
69
69
70 chunks : 3
70 chunks : 3
71 empty : 1 (33.33%)
71 empty : 1 (33.33%)
72 0x75 (u) : 2 (66.67%)
72 0x75 (u) : 2 (66.67%)
73 chunks size : 88
73 chunks size : 88
74 empty : 0 ( 0.00%)
74 empty : 0 ( 0.00%)
75 0x75 (u) : 88 (100.00%)
75 0x75 (u) : 88 (100.00%)
76
76
77 avg chain length : 0
77 avg chain length : 0
78 max chain length : 0
78 max chain length : 0
79 max chain reach : 44
79 max chain reach : 44
80 compression ratio : 0
80 compression ratio : 0
81
81
82 uncompressed data size (min/max/avg) : 0 / 43 / 28
82 uncompressed data size (min/max/avg) : 0 / 43 / 28
83 full revision size (min/max/avg) : 44 / 44 / 44
83 full revision size (min/max/avg) : 44 / 44 / 44
84 inter-snapshot size (min/max/avg) : 0 / 0 / 0
84 inter-snapshot size (min/max/avg) : 0 / 0 / 0
85 delta size (min/max/avg) : 0 / 0 / 0
85 delta size (min/max/avg) : 0 / 0 / 0
86 $ hg debugrevlog a
86 $ hg debugrevlog a
87 format : 1
87 format : 1
88 flags : inline, generaldelta
88 flags : inline, generaldelta
89
89
90 revisions : 1
90 revisions : 1
91 merges : 0 ( 0.00%)
91 merges : 0 ( 0.00%)
92 normal : 1 (100.00%)
92 normal : 1 (100.00%)
93 revisions : 1
93 revisions : 1
94 empty : 0 ( 0.00%)
94 empty : 0 ( 0.00%)
95 text : 0 (100.00%)
95 text : 0 (100.00%)
96 delta : 0 (100.00%)
96 delta : 0 (100.00%)
97 snapshot : 1 (100.00%)
97 snapshot : 1 (100.00%)
98 lvl-0 : 1 (100.00%)
98 lvl-0 : 1 (100.00%)
99 deltas : 0 ( 0.00%)
99 deltas : 0 ( 0.00%)
100 revision size : 3
100 revision size : 3
101 snapshot : 3 (100.00%)
101 snapshot : 3 (100.00%)
102 lvl-0 : 3 (100.00%)
102 lvl-0 : 3 (100.00%)
103 deltas : 0 ( 0.00%)
103 deltas : 0 ( 0.00%)
104
104
105 chunks : 1
105 chunks : 1
106 0x75 (u) : 1 (100.00%)
106 0x75 (u) : 1 (100.00%)
107 chunks size : 3
107 chunks size : 3
108 0x75 (u) : 3 (100.00%)
108 0x75 (u) : 3 (100.00%)
109
109
110 avg chain length : 0
110 avg chain length : 0
111 max chain length : 0
111 max chain length : 0
112 max chain reach : 3
112 max chain reach : 3
113 compression ratio : 0
113 compression ratio : 0
114
114
115 uncompressed data size (min/max/avg) : 2 / 2 / 2
115 uncompressed data size (min/max/avg) : 2 / 2 / 2
116 full revision size (min/max/avg) : 3 / 3 / 3
116 full revision size (min/max/avg) : 3 / 3 / 3
117 inter-snapshot size (min/max/avg) : 0 / 0 / 0
117 inter-snapshot size (min/max/avg) : 0 / 0 / 0
118 delta size (min/max/avg) : 0 / 0 / 0
118 delta size (min/max/avg) : 0 / 0 / 0
119 #endif
119 #endif
120
120
121 Test debugindex, with and without the --verbose/--debug flag
121 Test debugindex, with and without the --verbose/--debug flag
122 $ hg debugrevlogindex a
122 $ hg debugrevlogindex a
123 rev linkrev nodeid p1 p2
123 rev linkrev nodeid p1 p2
124 0 0 b789fdd96dc2 000000000000 000000000000
124 0 0 b789fdd96dc2 000000000000 000000000000
125
125
126 #if no-reposimplestore
126 #if no-reposimplestore
127 $ hg --verbose debugrevlogindex a
127 $ hg --verbose debugrevlogindex a
128 rev offset length linkrev nodeid p1 p2
128 rev offset length linkrev nodeid p1 p2
129 0 0 3 0 b789fdd96dc2 000000000000 000000000000
129 0 0 3 0 b789fdd96dc2 000000000000 000000000000
130
130
131 $ hg --debug debugrevlogindex a
131 $ hg --debug debugrevlogindex a
132 rev offset length linkrev nodeid p1 p2
132 rev offset length linkrev nodeid p1 p2
133 0 0 3 0 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
133 0 0 3 0 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
134 #endif
134 #endif
135
135
136 $ hg debugrevlogindex -f 1 a
136 $ hg debugrevlogindex -f 1 a
137 rev flag size link p1 p2 nodeid
137 rev flag size link p1 p2 nodeid
138 0 0000 2 0 -1 -1 b789fdd96dc2
138 0 0000 2 0 -1 -1 b789fdd96dc2
139
139
140 #if no-reposimplestore
140 #if no-reposimplestore
141 $ hg --verbose debugrevlogindex -f 1 a
141 $ hg --verbose debugrevlogindex -f 1 a
142 rev flag offset length size link p1 p2 nodeid
142 rev flag offset length size link p1 p2 nodeid
143 0 0000 0 3 2 0 -1 -1 b789fdd96dc2
143 0 0000 0 3 2 0 -1 -1 b789fdd96dc2
144
144
145 $ hg --debug debugrevlogindex -f 1 a
145 $ hg --debug debugrevlogindex -f 1 a
146 rev flag offset length size link p1 p2 nodeid
146 rev flag offset length size link p1 p2 nodeid
147 0 0000 0 3 2 0 -1 -1 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
147 0 0000 0 3 2 0 -1 -1 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
148 #endif
148 #endif
149
149
150 $ hg debugindex -c
150 $ hg debugindex -c
151 rev linkrev nodeid p1 p2
151 rev linkrev nodeid p1 p2
152 0 0 07f494440405 000000000000 000000000000
152 0 0 07f494440405 000000000000 000000000000
153 1 1 8cccb4b5fec2 07f494440405 000000000000
153 1 1 8cccb4b5fec2 07f494440405 000000000000
154 2 2 b1e228c512c5 8cccb4b5fec2 000000000000
154 2 2 b1e228c512c5 8cccb4b5fec2 000000000000
155 $ hg debugindex -c --debug
155 $ hg debugindex -c --debug
156 rev linkrev nodeid p1 p2
156 rev linkrev nodeid p1 p2
157 0 0 07f4944404050f47db2e5c5071e0e84e7a27bba9 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
157 0 0 07f4944404050f47db2e5c5071e0e84e7a27bba9 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
158 1 1 8cccb4b5fec20cafeb99dd01c26d4dee8ea4388a 07f4944404050f47db2e5c5071e0e84e7a27bba9 0000000000000000000000000000000000000000
158 1 1 8cccb4b5fec20cafeb99dd01c26d4dee8ea4388a 07f4944404050f47db2e5c5071e0e84e7a27bba9 0000000000000000000000000000000000000000
159 2 2 b1e228c512c5d7066d70562ed839c3323a62d6d2 8cccb4b5fec20cafeb99dd01c26d4dee8ea4388a 0000000000000000000000000000000000000000
159 2 2 b1e228c512c5d7066d70562ed839c3323a62d6d2 8cccb4b5fec20cafeb99dd01c26d4dee8ea4388a 0000000000000000000000000000000000000000
160 $ hg debugindex -m
160 $ hg debugindex -m
161 rev linkrev nodeid p1 p2
161 rev linkrev nodeid p1 p2
162 0 0 a0c8bcbbb45c 000000000000 000000000000
162 0 0 a0c8bcbbb45c 000000000000 000000000000
163 1 1 57faf8a737ae a0c8bcbbb45c 000000000000
163 1 1 57faf8a737ae a0c8bcbbb45c 000000000000
164 2 2 a35b10320954 57faf8a737ae 000000000000
164 2 2 a35b10320954 57faf8a737ae 000000000000
165 $ hg debugindex -m --debug
165 $ hg debugindex -m --debug
166 rev linkrev nodeid p1 p2
166 rev linkrev nodeid p1 p2
167 0 0 a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
167 0 0 a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
168 1 1 57faf8a737ae7faf490582941a82319ba6529dca a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 0000000000000000000000000000000000000000
168 1 1 57faf8a737ae7faf490582941a82319ba6529dca a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 0000000000000000000000000000000000000000
169 2 2 a35b103209548032201c16c7688cb2657f037a38 57faf8a737ae7faf490582941a82319ba6529dca 0000000000000000000000000000000000000000
169 2 2 a35b103209548032201c16c7688cb2657f037a38 57faf8a737ae7faf490582941a82319ba6529dca 0000000000000000000000000000000000000000
170 $ hg debugindex a
170 $ hg debugindex a
171 rev linkrev nodeid p1 p2
171 rev linkrev nodeid p1 p2
172 0 0 b789fdd96dc2 000000000000 000000000000
172 0 0 b789fdd96dc2 000000000000 000000000000
173 $ hg debugindex --debug a
173 $ hg debugindex --debug a
174 rev linkrev nodeid p1 p2
174 rev linkrev nodeid p1 p2
175 0 0 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
175 0 0 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
176
176
177 debugdelta chain basic output
177 debugdelta chain basic output
178
178
179 #if reporevlogstore
179 #if reporevlogstore
180 $ hg debugindexstats
180 $ hg debugindexstats
181 node trie capacity: 4
181 node trie capacity: 4
182 node trie count: 2
182 node trie count: 2
183 node trie depth: 1
183 node trie depth: 1
184 node trie last rev scanned: -1
184 node trie last rev scanned: -1
185 node trie lookups: 4
185 node trie lookups: 4
186 node trie misses: 1
186 node trie misses: 1
187 node trie splits: 1
187 node trie splits: 1
188 revs in memory: 3
188 revs in memory: 3
189
189
190 $ hg debugdeltachain -m
190 $ hg debugdeltachain -m
191 rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio
191 rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio
192 0 1 1 -1 base 44 43 44 1.02326 44 0 0.00000
192 0 1 1 -1 base 44 43 44 1.02326 44 0 0.00000
193 1 2 1 -1 base 0 0 0 0.00000 0 0 0.00000
193 1 2 1 -1 base 0 0 0 0.00000 0 0 0.00000
194 2 3 1 -1 base 44 43 44 1.02326 44 0 0.00000
194 2 3 1 -1 base 44 43 44 1.02326 44 0 0.00000
195
195
196 $ hg debugdeltachain -m -T '{rev} {chainid} {chainlen}\n'
196 $ hg debugdeltachain -m -T '{rev} {chainid} {chainlen}\n'
197 0 1 1
197 0 1 1
198 1 2 1
198 1 2 1
199 2 3 1
199 2 3 1
200
200
201 $ hg debugdeltachain -m -Tjson
201 $ hg debugdeltachain -m -Tjson
202 [
202 [
203 {
203 {
204 "chainid": 1,
204 "chainid": 1,
205 "chainlen": 1,
205 "chainlen": 1,
206 "chainratio": 1.02325581395, (no-py3k !)
206 "chainratio": 1.02325581395, (no-py3 !)
207 "chainratio": 1.0232558139534884, (py3k !)
207 "chainratio": 1.0232558139534884, (py3 !)
208 "chainsize": 44,
208 "chainsize": 44,
209 "compsize": 44,
209 "compsize": 44,
210 "deltatype": "base",
210 "deltatype": "base",
211 "extradist": 0,
211 "extradist": 0,
212 "extraratio": 0.0,
212 "extraratio": 0.0,
213 "lindist": 44,
213 "lindist": 44,
214 "prevrev": -1,
214 "prevrev": -1,
215 "rev": 0,
215 "rev": 0,
216 "uncompsize": 43
216 "uncompsize": 43
217 },
217 },
218 {
218 {
219 "chainid": 2,
219 "chainid": 2,
220 "chainlen": 1,
220 "chainlen": 1,
221 "chainratio": 0,
221 "chainratio": 0,
222 "chainsize": 0,
222 "chainsize": 0,
223 "compsize": 0,
223 "compsize": 0,
224 "deltatype": "base",
224 "deltatype": "base",
225 "extradist": 0,
225 "extradist": 0,
226 "extraratio": 0,
226 "extraratio": 0,
227 "lindist": 0,
227 "lindist": 0,
228 "prevrev": -1,
228 "prevrev": -1,
229 "rev": 1,
229 "rev": 1,
230 "uncompsize": 0
230 "uncompsize": 0
231 },
231 },
232 {
232 {
233 "chainid": 3,
233 "chainid": 3,
234 "chainlen": 1,
234 "chainlen": 1,
235 "chainratio": 1.02325581395, (no-py3k !)
235 "chainratio": 1.02325581395, (no-py3 !)
236 "chainratio": 1.0232558139534884, (py3k !)
236 "chainratio": 1.0232558139534884, (py3 !)
237 "chainsize": 44,
237 "chainsize": 44,
238 "compsize": 44,
238 "compsize": 44,
239 "deltatype": "base",
239 "deltatype": "base",
240 "extradist": 0,
240 "extradist": 0,
241 "extraratio": 0.0,
241 "extraratio": 0.0,
242 "lindist": 44,
242 "lindist": 44,
243 "prevrev": -1,
243 "prevrev": -1,
244 "rev": 2,
244 "rev": 2,
245 "uncompsize": 43
245 "uncompsize": 43
246 }
246 }
247 ]
247 ]
248
248
249 debugdelta chain with sparse read enabled
249 debugdelta chain with sparse read enabled
250
250
251 $ cat >> $HGRCPATH <<EOF
251 $ cat >> $HGRCPATH <<EOF
252 > [experimental]
252 > [experimental]
253 > sparse-read = True
253 > sparse-read = True
254 > EOF
254 > EOF
255 $ hg debugdeltachain -m
255 $ hg debugdeltachain -m
256 rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio readsize largestblk rddensity srchunks
256 rev chain# chainlen prev delta size rawsize chainsize ratio lindist extradist extraratio readsize largestblk rddensity srchunks
257 0 1 1 -1 base 44 43 44 1.02326 44 0 0.00000 44 44 1.00000 1
257 0 1 1 -1 base 44 43 44 1.02326 44 0 0.00000 44 44 1.00000 1
258 1 2 1 -1 base 0 0 0 0.00000 0 0 0.00000 0 0 1.00000 1
258 1 2 1 -1 base 0 0 0 0.00000 0 0 0.00000 0 0 1.00000 1
259 2 3 1 -1 base 44 43 44 1.02326 44 0 0.00000 44 44 1.00000 1
259 2 3 1 -1 base 44 43 44 1.02326 44 0 0.00000 44 44 1.00000 1
260
260
261 $ hg debugdeltachain -m -T '{rev} {chainid} {chainlen} {readsize} {largestblock} {readdensity}\n'
261 $ hg debugdeltachain -m -T '{rev} {chainid} {chainlen} {readsize} {largestblock} {readdensity}\n'
262 0 1 1 44 44 1.0
262 0 1 1 44 44 1.0
263 1 2 1 0 0 1
263 1 2 1 0 0 1
264 2 3 1 44 44 1.0
264 2 3 1 44 44 1.0
265
265
266 $ hg debugdeltachain -m -Tjson
266 $ hg debugdeltachain -m -Tjson
267 [
267 [
268 {
268 {
269 "chainid": 1,
269 "chainid": 1,
270 "chainlen": 1,
270 "chainlen": 1,
271 "chainratio": 1.02325581395, (no-py3k !)
271 "chainratio": 1.02325581395, (no-py3 !)
272 "chainratio": 1.0232558139534884, (py3k !)
272 "chainratio": 1.0232558139534884, (py3 !)
273 "chainsize": 44,
273 "chainsize": 44,
274 "compsize": 44,
274 "compsize": 44,
275 "deltatype": "base",
275 "deltatype": "base",
276 "extradist": 0,
276 "extradist": 0,
277 "extraratio": 0.0,
277 "extraratio": 0.0,
278 "largestblock": 44,
278 "largestblock": 44,
279 "lindist": 44,
279 "lindist": 44,
280 "prevrev": -1,
280 "prevrev": -1,
281 "readdensity": 1.0,
281 "readdensity": 1.0,
282 "readsize": 44,
282 "readsize": 44,
283 "rev": 0,
283 "rev": 0,
284 "srchunks": 1,
284 "srchunks": 1,
285 "uncompsize": 43
285 "uncompsize": 43
286 },
286 },
287 {
287 {
288 "chainid": 2,
288 "chainid": 2,
289 "chainlen": 1,
289 "chainlen": 1,
290 "chainratio": 0,
290 "chainratio": 0,
291 "chainsize": 0,
291 "chainsize": 0,
292 "compsize": 0,
292 "compsize": 0,
293 "deltatype": "base",
293 "deltatype": "base",
294 "extradist": 0,
294 "extradist": 0,
295 "extraratio": 0,
295 "extraratio": 0,
296 "largestblock": 0,
296 "largestblock": 0,
297 "lindist": 0,
297 "lindist": 0,
298 "prevrev": -1,
298 "prevrev": -1,
299 "readdensity": 1,
299 "readdensity": 1,
300 "readsize": 0,
300 "readsize": 0,
301 "rev": 1,
301 "rev": 1,
302 "srchunks": 1,
302 "srchunks": 1,
303 "uncompsize": 0
303 "uncompsize": 0
304 },
304 },
305 {
305 {
306 "chainid": 3,
306 "chainid": 3,
307 "chainlen": 1,
307 "chainlen": 1,
308 "chainratio": 1.02325581395, (no-py3k !)
308 "chainratio": 1.02325581395, (no-py3 !)
309 "chainratio": 1.0232558139534884, (py3k !)
309 "chainratio": 1.0232558139534884, (py3 !)
310 "chainsize": 44,
310 "chainsize": 44,
311 "compsize": 44,
311 "compsize": 44,
312 "deltatype": "base",
312 "deltatype": "base",
313 "extradist": 0,
313 "extradist": 0,
314 "extraratio": 0.0,
314 "extraratio": 0.0,
315 "largestblock": 44,
315 "largestblock": 44,
316 "lindist": 44,
316 "lindist": 44,
317 "prevrev": -1,
317 "prevrev": -1,
318 "readdensity": 1.0,
318 "readdensity": 1.0,
319 "readsize": 44,
319 "readsize": 44,
320 "rev": 2,
320 "rev": 2,
321 "srchunks": 1,
321 "srchunks": 1,
322 "uncompsize": 43
322 "uncompsize": 43
323 }
323 }
324 ]
324 ]
325
325
326 $ printf "This test checks things.\n" >> a
326 $ printf "This test checks things.\n" >> a
327 $ hg ci -m a
327 $ hg ci -m a
328 $ hg branch other
328 $ hg branch other
329 marked working directory as branch other
329 marked working directory as branch other
330 (branches are permanent and global, did you want a bookmark?)
330 (branches are permanent and global, did you want a bookmark?)
331 $ for i in `$TESTDIR/seq.py 5`; do
331 $ for i in `$TESTDIR/seq.py 5`; do
332 > printf "shorter ${i}" >> a
332 > printf "shorter ${i}" >> a
333 > hg ci -m "a other:$i"
333 > hg ci -m "a other:$i"
334 > hg up -q default
334 > hg up -q default
335 > printf "for the branch default we want longer chains: ${i}" >> a
335 > printf "for the branch default we want longer chains: ${i}" >> a
336 > hg ci -m "a default:$i"
336 > hg ci -m "a default:$i"
337 > hg up -q other
337 > hg up -q other
338 > done
338 > done
339 $ hg debugdeltachain a -T '{rev} {srchunks}\n' \
339 $ hg debugdeltachain a -T '{rev} {srchunks}\n' \
340 > --config experimental.sparse-read.density-threshold=0.50 \
340 > --config experimental.sparse-read.density-threshold=0.50 \
341 > --config experimental.sparse-read.min-gap-size=0
341 > --config experimental.sparse-read.min-gap-size=0
342 0 1
342 0 1
343 1 1
343 1 1
344 2 1
344 2 1
345 3 1
345 3 1
346 4 1
346 4 1
347 5 1
347 5 1
348 6 1
348 6 1
349 7 1
349 7 1
350 8 1
350 8 1
351 9 1
351 9 1
352 10 2
352 10 2
353 11 1
353 11 1
354 $ hg --config extensions.strip= strip --no-backup -r 1
354 $ hg --config extensions.strip= strip --no-backup -r 1
355 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
355 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
356
356
357 Test max chain len
357 Test max chain len
358 $ cat >> $HGRCPATH << EOF
358 $ cat >> $HGRCPATH << EOF
359 > [format]
359 > [format]
360 > maxchainlen=4
360 > maxchainlen=4
361 > EOF
361 > EOF
362
362
363 $ printf "This test checks if maxchainlen config value is respected also it can serve as basic test for debugrevlog -d <file>.\n" >> a
363 $ printf "This test checks if maxchainlen config value is respected also it can serve as basic test for debugrevlog -d <file>.\n" >> a
364 $ hg ci -m a
364 $ hg ci -m a
365 $ printf "b\n" >> a
365 $ printf "b\n" >> a
366 $ hg ci -m a
366 $ hg ci -m a
367 $ printf "c\n" >> a
367 $ printf "c\n" >> a
368 $ hg ci -m a
368 $ hg ci -m a
369 $ printf "d\n" >> a
369 $ printf "d\n" >> a
370 $ hg ci -m a
370 $ hg ci -m a
371 $ printf "e\n" >> a
371 $ printf "e\n" >> a
372 $ hg ci -m a
372 $ hg ci -m a
373 $ printf "f\n" >> a
373 $ printf "f\n" >> a
374 $ hg ci -m a
374 $ hg ci -m a
375 $ printf 'g\n' >> a
375 $ printf 'g\n' >> a
376 $ hg ci -m a
376 $ hg ci -m a
377 $ printf 'h\n' >> a
377 $ printf 'h\n' >> a
378 $ hg ci -m a
378 $ hg ci -m a
379
379
380 $ hg debugrevlog -d a
380 $ hg debugrevlog -d a
381 # rev p1rev p2rev start end deltastart base p1 p2 rawsize totalsize compression heads chainlen
381 # rev p1rev p2rev start end deltastart base p1 p2 rawsize totalsize compression heads chainlen
382 0 -1 -1 0 ??? 0 0 0 0 ??? ???? ? 1 0 (glob)
382 0 -1 -1 0 ??? 0 0 0 0 ??? ???? ? 1 0 (glob)
383 1 0 -1 ??? ??? 0 0 0 0 ??? ???? ? 1 1 (glob)
383 1 0 -1 ??? ??? 0 0 0 0 ??? ???? ? 1 1 (glob)
384 2 1 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 2 (glob)
384 2 1 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 2 (glob)
385 3 2 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 3 (glob)
385 3 2 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 3 (glob)
386 4 3 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 4 (glob)
386 4 3 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 4 (glob)
387 5 4 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 0 (glob)
387 5 4 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 0 (glob)
388 6 5 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 1 (glob)
388 6 5 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 1 (glob)
389 7 6 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 2 (glob)
389 7 6 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 2 (glob)
390 8 7 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 3 (glob)
390 8 7 -1 ??? ??? ??? ??? ??? 0 ??? ???? ? 1 3 (glob)
391 #endif
391 #endif
392
392
393 Test debuglocks command:
393 Test debuglocks command:
394
394
395 $ hg debuglocks
395 $ hg debuglocks
396 lock: free
396 lock: free
397 wlock: free
397 wlock: free
398
398
399 * Test setting the lock
399 * Test setting the lock
400
400
401 waitlock <file> will wait for file to be created. If it isn't in a reasonable
401 waitlock <file> will wait for file to be created. If it isn't in a reasonable
402 amount of time, displays error message and returns 1
402 amount of time, displays error message and returns 1
403 $ waitlock() {
403 $ waitlock() {
404 > start=`date +%s`
404 > start=`date +%s`
405 > timeout=5
405 > timeout=5
406 > while [ \( ! -f $1 \) -a \( ! -L $1 \) ]; do
406 > while [ \( ! -f $1 \) -a \( ! -L $1 \) ]; do
407 > now=`date +%s`
407 > now=`date +%s`
408 > if [ "`expr $now - $start`" -gt $timeout ]; then
408 > if [ "`expr $now - $start`" -gt $timeout ]; then
409 > echo "timeout: $1 was not created in $timeout seconds"
409 > echo "timeout: $1 was not created in $timeout seconds"
410 > return 1
410 > return 1
411 > fi
411 > fi
412 > sleep 0.1
412 > sleep 0.1
413 > done
413 > done
414 > }
414 > }
415 $ dolock() {
415 $ dolock() {
416 > {
416 > {
417 > waitlock .hg/unlock
417 > waitlock .hg/unlock
418 > rm -f .hg/unlock
418 > rm -f .hg/unlock
419 > echo y
419 > echo y
420 > } | hg debuglocks "$@" > /dev/null
420 > } | hg debuglocks "$@" > /dev/null
421 > }
421 > }
422 $ dolock -s &
422 $ dolock -s &
423 $ waitlock .hg/store/lock
423 $ waitlock .hg/store/lock
424
424
425 $ hg debuglocks
425 $ hg debuglocks
426 lock: user *, process * (*s) (glob)
426 lock: user *, process * (*s) (glob)
427 wlock: free
427 wlock: free
428 [1]
428 [1]
429 $ touch .hg/unlock
429 $ touch .hg/unlock
430 $ wait
430 $ wait
431 $ [ -f .hg/store/lock ] || echo "There is no lock"
431 $ [ -f .hg/store/lock ] || echo "There is no lock"
432 There is no lock
432 There is no lock
433
433
434 * Test setting the wlock
434 * Test setting the wlock
435
435
436 $ dolock -S &
436 $ dolock -S &
437 $ waitlock .hg/wlock
437 $ waitlock .hg/wlock
438
438
439 $ hg debuglocks
439 $ hg debuglocks
440 lock: free
440 lock: free
441 wlock: user *, process * (*s) (glob)
441 wlock: user *, process * (*s) (glob)
442 [1]
442 [1]
443 $ touch .hg/unlock
443 $ touch .hg/unlock
444 $ wait
444 $ wait
445 $ [ -f .hg/wlock ] || echo "There is no wlock"
445 $ [ -f .hg/wlock ] || echo "There is no wlock"
446 There is no wlock
446 There is no wlock
447
447
448 * Test setting both locks
448 * Test setting both locks
449
449
450 $ dolock -Ss &
450 $ dolock -Ss &
451 $ waitlock .hg/wlock && waitlock .hg/store/lock
451 $ waitlock .hg/wlock && waitlock .hg/store/lock
452
452
453 $ hg debuglocks
453 $ hg debuglocks
454 lock: user *, process * (*s) (glob)
454 lock: user *, process * (*s) (glob)
455 wlock: user *, process * (*s) (glob)
455 wlock: user *, process * (*s) (glob)
456 [2]
456 [2]
457
457
458 * Test failing to set a lock
458 * Test failing to set a lock
459
459
460 $ hg debuglocks -s
460 $ hg debuglocks -s
461 abort: lock is already held
461 abort: lock is already held
462 [255]
462 [255]
463
463
464 $ hg debuglocks -S
464 $ hg debuglocks -S
465 abort: wlock is already held
465 abort: wlock is already held
466 [255]
466 [255]
467
467
468 $ touch .hg/unlock
468 $ touch .hg/unlock
469 $ wait
469 $ wait
470
470
471 $ hg debuglocks
471 $ hg debuglocks
472 lock: free
472 lock: free
473 wlock: free
473 wlock: free
474
474
475 * Test forcing the lock
475 * Test forcing the lock
476
476
477 $ dolock -s &
477 $ dolock -s &
478 $ waitlock .hg/store/lock
478 $ waitlock .hg/store/lock
479
479
480 $ hg debuglocks
480 $ hg debuglocks
481 lock: user *, process * (*s) (glob)
481 lock: user *, process * (*s) (glob)
482 wlock: free
482 wlock: free
483 [1]
483 [1]
484
484
485 $ hg debuglocks -L
485 $ hg debuglocks -L
486
486
487 $ hg debuglocks
487 $ hg debuglocks
488 lock: free
488 lock: free
489 wlock: free
489 wlock: free
490
490
491 $ touch .hg/unlock
491 $ touch .hg/unlock
492 $ wait
492 $ wait
493
493
494 * Test forcing the wlock
494 * Test forcing the wlock
495
495
496 $ dolock -S &
496 $ dolock -S &
497 $ waitlock .hg/wlock
497 $ waitlock .hg/wlock
498
498
499 $ hg debuglocks
499 $ hg debuglocks
500 lock: free
500 lock: free
501 wlock: user *, process * (*s) (glob)
501 wlock: user *, process * (*s) (glob)
502 [1]
502 [1]
503
503
504 $ hg debuglocks -W
504 $ hg debuglocks -W
505
505
506 $ hg debuglocks
506 $ hg debuglocks
507 lock: free
507 lock: free
508 wlock: free
508 wlock: free
509
509
510 $ touch .hg/unlock
510 $ touch .hg/unlock
511 $ wait
511 $ wait
512
512
513 Test WdirUnsupported exception
513 Test WdirUnsupported exception
514
514
515 $ hg debugdata -c ffffffffffffffffffffffffffffffffffffffff
515 $ hg debugdata -c ffffffffffffffffffffffffffffffffffffffff
516 abort: working directory revision cannot be specified
516 abort: working directory revision cannot be specified
517 [255]
517 [255]
518
518
519 Test cache warming command
519 Test cache warming command
520
520
521 $ rm -rf .hg/cache/
521 $ rm -rf .hg/cache/
522 $ hg debugupdatecaches --debug
522 $ hg debugupdatecaches --debug
523 updating the branch cache
523 updating the branch cache
524 $ ls -r .hg/cache/*
524 $ ls -r .hg/cache/*
525 .hg/cache/rbc-revs-v1
525 .hg/cache/rbc-revs-v1
526 .hg/cache/rbc-names-v1
526 .hg/cache/rbc-names-v1
527 .hg/cache/manifestfulltextcache (reporevlogstore !)
527 .hg/cache/manifestfulltextcache (reporevlogstore !)
528 .hg/cache/branch2-served
528 .hg/cache/branch2-served
529
529
530 Test debugcolor
530 Test debugcolor
531
531
532 #if no-windows
532 #if no-windows
533 $ hg debugcolor --style --color always | egrep 'mode|style|log\.'
533 $ hg debugcolor --style --color always | egrep 'mode|style|log\.'
534 color mode: 'ansi'
534 color mode: 'ansi'
535 available style:
535 available style:
536 \x1b[0;33mlog.changeset\x1b[0m: \x1b[0;33myellow\x1b[0m (esc)
536 \x1b[0;33mlog.changeset\x1b[0m: \x1b[0;33myellow\x1b[0m (esc)
537 #endif
537 #endif
538
538
539 $ hg debugcolor --style --color never
539 $ hg debugcolor --style --color never
540 color mode: None
540 color mode: None
541 available style:
541 available style:
542
542
543 $ cd ..
543 $ cd ..
544
544
545 Test internal debugstacktrace command
545 Test internal debugstacktrace command
546
546
547 $ cat > debugstacktrace.py << EOF
547 $ cat > debugstacktrace.py << EOF
548 > from __future__ import absolute_import
548 > from __future__ import absolute_import
549 > from mercurial import (
549 > from mercurial import (
550 > pycompat,
550 > pycompat,
551 > util,
551 > util,
552 > )
552 > )
553 > def f():
553 > def f():
554 > util.debugstacktrace(f=pycompat.stdout)
554 > util.debugstacktrace(f=pycompat.stdout)
555 > g()
555 > g()
556 > def g():
556 > def g():
557 > util.dst(b'hello from g\\n', skip=1)
557 > util.dst(b'hello from g\\n', skip=1)
558 > h()
558 > h()
559 > def h():
559 > def h():
560 > util.dst(b'hi ...\\nfrom h hidden in g', 1, depth=2)
560 > util.dst(b'hi ...\\nfrom h hidden in g', 1, depth=2)
561 > f()
561 > f()
562 > EOF
562 > EOF
563 $ "$PYTHON" debugstacktrace.py
563 $ "$PYTHON" debugstacktrace.py
564 stacktrace at:
564 stacktrace at:
565 debugstacktrace.py:14 in * (glob)
565 debugstacktrace.py:14 in * (glob)
566 debugstacktrace.py:7 in f
566 debugstacktrace.py:7 in f
567 hello from g at:
567 hello from g at:
568 debugstacktrace.py:14 in * (glob)
568 debugstacktrace.py:14 in * (glob)
569 debugstacktrace.py:8 in f
569 debugstacktrace.py:8 in f
570 hi ...
570 hi ...
571 from h hidden in g at:
571 from h hidden in g at:
572 debugstacktrace.py:8 in f
572 debugstacktrace.py:8 in f
573 debugstacktrace.py:11 in g
573 debugstacktrace.py:11 in g
574
574
575 Test debugcapabilities command:
575 Test debugcapabilities command:
576
576
577 $ hg debugcapabilities ./debugrevlog/
577 $ hg debugcapabilities ./debugrevlog/
578 Main capabilities:
578 Main capabilities:
579 branchmap
579 branchmap
580 $USUAL_BUNDLE2_CAPS$
580 $USUAL_BUNDLE2_CAPS$
581 getbundle
581 getbundle
582 known
582 known
583 lookup
583 lookup
584 pushkey
584 pushkey
585 unbundle
585 unbundle
586 Bundle2 capabilities:
586 Bundle2 capabilities:
587 HG20
587 HG20
588 bookmarks
588 bookmarks
589 changegroup
589 changegroup
590 01
590 01
591 02
591 02
592 digests
592 digests
593 md5
593 md5
594 sha1
594 sha1
595 sha512
595 sha512
596 error
596 error
597 abort
597 abort
598 unsupportedcontent
598 unsupportedcontent
599 pushraced
599 pushraced
600 pushkey
600 pushkey
601 hgtagsfnodes
601 hgtagsfnodes
602 listkeys
602 listkeys
603 phases
603 phases
604 heads
604 heads
605 pushkey
605 pushkey
606 remote-changegroup
606 remote-changegroup
607 http
607 http
608 https
608 https
609 rev-branch-cache
609 rev-branch-cache
610 stream
610 stream
611 v2
611 v2
612
612
613 Test debugpeer
613 Test debugpeer
614
614
615 $ hg --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" debugpeer ssh://user@dummy/debugrevlog
615 $ hg --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" debugpeer ssh://user@dummy/debugrevlog
616 url: ssh://user@dummy/debugrevlog
616 url: ssh://user@dummy/debugrevlog
617 local: no
617 local: no
618 pushable: yes
618 pushable: yes
619
619
620 $ hg --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" --debug debugpeer ssh://user@dummy/debugrevlog
620 $ hg --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" --debug debugpeer ssh://user@dummy/debugrevlog
621 running "*" "*/tests/dummyssh" 'user@dummy' 'hg -R debugrevlog serve --stdio' (glob) (no-windows !)
621 running "*" "*/tests/dummyssh" 'user@dummy' 'hg -R debugrevlog serve --stdio' (glob) (no-windows !)
622 running "*" "*\tests/dummyssh" "user@dummy" "hg -R debugrevlog serve --stdio" (glob) (windows !)
622 running "*" "*\tests/dummyssh" "user@dummy" "hg -R debugrevlog serve --stdio" (glob) (windows !)
623 devel-peer-request: hello+between
623 devel-peer-request: hello+between
624 devel-peer-request: pairs: 81 bytes
624 devel-peer-request: pairs: 81 bytes
625 sending hello command
625 sending hello command
626 sending between command
626 sending between command
627 remote: 427
627 remote: 427
628 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
628 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
629 remote: 1
629 remote: 1
630 devel-peer-request: protocaps
630 devel-peer-request: protocaps
631 devel-peer-request: caps: * bytes (glob)
631 devel-peer-request: caps: * bytes (glob)
632 sending protocaps command
632 sending protocaps command
633 url: ssh://user@dummy/debugrevlog
633 url: ssh://user@dummy/debugrevlog
634 local: no
634 local: no
635 pushable: yes
635 pushable: yes
@@ -1,1790 +1,1790 b''
1 Test basic extension support
1 Test basic extension support
2 $ cat > unflush.py <<EOF
2 $ cat > unflush.py <<EOF
3 > import sys
3 > import sys
4 > from mercurial import pycompat
4 > from mercurial import pycompat
5 > if pycompat.ispy3:
5 > if pycompat.ispy3:
6 > # no changes required
6 > # no changes required
7 > sys.exit(0)
7 > sys.exit(0)
8 > with open(sys.argv[1], 'rb') as f:
8 > with open(sys.argv[1], 'rb') as f:
9 > data = f.read()
9 > data = f.read()
10 > with open(sys.argv[1], 'wb') as f:
10 > with open(sys.argv[1], 'wb') as f:
11 > f.write(data.replace(b', flush=True', b''))
11 > f.write(data.replace(b', flush=True', b''))
12 > EOF
12 > EOF
13
13
14 $ cat > foobar.py <<EOF
14 $ cat > foobar.py <<EOF
15 > import os
15 > import os
16 > from mercurial import commands, registrar
16 > from mercurial import commands, registrar
17 > cmdtable = {}
17 > cmdtable = {}
18 > command = registrar.command(cmdtable)
18 > command = registrar.command(cmdtable)
19 > configtable = {}
19 > configtable = {}
20 > configitem = registrar.configitem(configtable)
20 > configitem = registrar.configitem(configtable)
21 > configitem(b'tests', b'foo', default=b"Foo")
21 > configitem(b'tests', b'foo', default=b"Foo")
22 > def uisetup(ui):
22 > def uisetup(ui):
23 > ui.debug(b"uisetup called [debug]\\n")
23 > ui.debug(b"uisetup called [debug]\\n")
24 > ui.write(b"uisetup called\\n")
24 > ui.write(b"uisetup called\\n")
25 > ui.status(b"uisetup called [status]\\n")
25 > ui.status(b"uisetup called [status]\\n")
26 > ui.flush()
26 > ui.flush()
27 > def reposetup(ui, repo):
27 > def reposetup(ui, repo):
28 > ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
28 > ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
29 > ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
29 > ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
30 > ui.flush()
30 > ui.flush()
31 > @command(b'foo', [], b'hg foo')
31 > @command(b'foo', [], b'hg foo')
32 > def foo(ui, *args, **kwargs):
32 > def foo(ui, *args, **kwargs):
33 > foo = ui.config(b'tests', b'foo')
33 > foo = ui.config(b'tests', b'foo')
34 > ui.write(foo)
34 > ui.write(foo)
35 > ui.write(b"\\n")
35 > ui.write(b"\\n")
36 > @command(b'bar', [], b'hg bar', norepo=True)
36 > @command(b'bar', [], b'hg bar', norepo=True)
37 > def bar(ui, *args, **kwargs):
37 > def bar(ui, *args, **kwargs):
38 > ui.write(b"Bar\\n")
38 > ui.write(b"Bar\\n")
39 > EOF
39 > EOF
40 $ abspath=`pwd`/foobar.py
40 $ abspath=`pwd`/foobar.py
41
41
42 $ mkdir barfoo
42 $ mkdir barfoo
43 $ cp foobar.py barfoo/__init__.py
43 $ cp foobar.py barfoo/__init__.py
44 $ barfoopath=`pwd`/barfoo
44 $ barfoopath=`pwd`/barfoo
45
45
46 $ hg init a
46 $ hg init a
47 $ cd a
47 $ cd a
48 $ echo foo > file
48 $ echo foo > file
49 $ hg add file
49 $ hg add file
50 $ hg commit -m 'add file'
50 $ hg commit -m 'add file'
51
51
52 $ echo '[extensions]' >> $HGRCPATH
52 $ echo '[extensions]' >> $HGRCPATH
53 $ echo "foobar = $abspath" >> $HGRCPATH
53 $ echo "foobar = $abspath" >> $HGRCPATH
54 $ hg foo
54 $ hg foo
55 uisetup called
55 uisetup called
56 uisetup called [status]
56 uisetup called [status]
57 reposetup called for a
57 reposetup called for a
58 ui == repo.ui
58 ui == repo.ui
59 reposetup called for a (chg !)
59 reposetup called for a (chg !)
60 ui == repo.ui (chg !)
60 ui == repo.ui (chg !)
61 Foo
61 Foo
62 $ hg foo --quiet
62 $ hg foo --quiet
63 uisetup called (no-chg !)
63 uisetup called (no-chg !)
64 reposetup called for a (chg !)
64 reposetup called for a (chg !)
65 ui == repo.ui
65 ui == repo.ui
66 Foo
66 Foo
67 $ hg foo --debug
67 $ hg foo --debug
68 uisetup called [debug] (no-chg !)
68 uisetup called [debug] (no-chg !)
69 uisetup called (no-chg !)
69 uisetup called (no-chg !)
70 uisetup called [status] (no-chg !)
70 uisetup called [status] (no-chg !)
71 reposetup called for a (chg !)
71 reposetup called for a (chg !)
72 ui == repo.ui
72 ui == repo.ui
73 Foo
73 Foo
74
74
75 $ cd ..
75 $ cd ..
76 $ hg clone a b
76 $ hg clone a b
77 uisetup called (no-chg !)
77 uisetup called (no-chg !)
78 uisetup called [status] (no-chg !)
78 uisetup called [status] (no-chg !)
79 reposetup called for a
79 reposetup called for a
80 ui == repo.ui
80 ui == repo.ui
81 reposetup called for b
81 reposetup called for b
82 ui == repo.ui
82 ui == repo.ui
83 updating to branch default
83 updating to branch default
84 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
85
85
86 $ hg bar
86 $ hg bar
87 uisetup called (no-chg !)
87 uisetup called (no-chg !)
88 uisetup called [status] (no-chg !)
88 uisetup called [status] (no-chg !)
89 Bar
89 Bar
90 $ echo 'foobar = !' >> $HGRCPATH
90 $ echo 'foobar = !' >> $HGRCPATH
91
91
92 module/__init__.py-style
92 module/__init__.py-style
93
93
94 $ echo "barfoo = $barfoopath" >> $HGRCPATH
94 $ echo "barfoo = $barfoopath" >> $HGRCPATH
95 $ cd a
95 $ cd a
96 $ hg foo
96 $ hg foo
97 uisetup called
97 uisetup called
98 uisetup called [status]
98 uisetup called [status]
99 reposetup called for a
99 reposetup called for a
100 ui == repo.ui
100 ui == repo.ui
101 reposetup called for a (chg !)
101 reposetup called for a (chg !)
102 ui == repo.ui (chg !)
102 ui == repo.ui (chg !)
103 Foo
103 Foo
104 $ echo 'barfoo = !' >> $HGRCPATH
104 $ echo 'barfoo = !' >> $HGRCPATH
105
105
106 Check that extensions are loaded in phases:
106 Check that extensions are loaded in phases:
107
107
108 $ cat > foo.py <<EOF
108 $ cat > foo.py <<EOF
109 > from __future__ import print_function
109 > from __future__ import print_function
110 > import os
110 > import os
111 > name = os.path.basename(__file__).rsplit('.', 1)[0]
111 > name = os.path.basename(__file__).rsplit('.', 1)[0]
112 > print("1) %s imported" % name, flush=True)
112 > print("1) %s imported" % name, flush=True)
113 > def uisetup(ui):
113 > def uisetup(ui):
114 > print("2) %s uisetup" % name, flush=True)
114 > print("2) %s uisetup" % name, flush=True)
115 > def extsetup():
115 > def extsetup():
116 > print("3) %s extsetup" % name, flush=True)
116 > print("3) %s extsetup" % name, flush=True)
117 > def reposetup(ui, repo):
117 > def reposetup(ui, repo):
118 > print("4) %s reposetup" % name, flush=True)
118 > print("4) %s reposetup" % name, flush=True)
119 >
119 >
120 > bytesname = name.encode('utf-8')
120 > bytesname = name.encode('utf-8')
121 > # custom predicate to check registration of functions at loading
121 > # custom predicate to check registration of functions at loading
122 > from mercurial import (
122 > from mercurial import (
123 > registrar,
123 > registrar,
124 > smartset,
124 > smartset,
125 > )
125 > )
126 > revsetpredicate = registrar.revsetpredicate()
126 > revsetpredicate = registrar.revsetpredicate()
127 > @revsetpredicate(bytesname, safe=True) # safe=True for query via hgweb
127 > @revsetpredicate(bytesname, safe=True) # safe=True for query via hgweb
128 > def custompredicate(repo, subset, x):
128 > def custompredicate(repo, subset, x):
129 > return smartset.baseset([r for r in subset if r in {0}])
129 > return smartset.baseset([r for r in subset if r in {0}])
130 > EOF
130 > EOF
131 $ $PYTHON $TESTTMP/unflush.py foo.py
131 $ $PYTHON $TESTTMP/unflush.py foo.py
132
132
133 $ cp foo.py bar.py
133 $ cp foo.py bar.py
134 $ echo 'foo = foo.py' >> $HGRCPATH
134 $ echo 'foo = foo.py' >> $HGRCPATH
135 $ echo 'bar = bar.py' >> $HGRCPATH
135 $ echo 'bar = bar.py' >> $HGRCPATH
136
136
137 Check normal command's load order of extensions and registration of functions
137 Check normal command's load order of extensions and registration of functions
138
138
139 $ hg log -r "foo() and bar()" -q
139 $ hg log -r "foo() and bar()" -q
140 1) foo imported
140 1) foo imported
141 1) bar imported
141 1) bar imported
142 2) foo uisetup
142 2) foo uisetup
143 2) bar uisetup
143 2) bar uisetup
144 3) foo extsetup
144 3) foo extsetup
145 3) bar extsetup
145 3) bar extsetup
146 4) foo reposetup
146 4) foo reposetup
147 4) bar reposetup
147 4) bar reposetup
148 0:c24b9ac61126
148 0:c24b9ac61126
149
149
150 Check hgweb's load order of extensions and registration of functions
150 Check hgweb's load order of extensions and registration of functions
151
151
152 $ cat > hgweb.cgi <<EOF
152 $ cat > hgweb.cgi <<EOF
153 > #!$PYTHON
153 > #!$PYTHON
154 > from mercurial import demandimport; demandimport.enable()
154 > from mercurial import demandimport; demandimport.enable()
155 > from mercurial.hgweb import hgweb
155 > from mercurial.hgweb import hgweb
156 > from mercurial.hgweb import wsgicgi
156 > from mercurial.hgweb import wsgicgi
157 > application = hgweb(b'.', b'test repo')
157 > application = hgweb(b'.', b'test repo')
158 > wsgicgi.launch(application)
158 > wsgicgi.launch(application)
159 > EOF
159 > EOF
160 $ . "$TESTDIR/cgienv"
160 $ . "$TESTDIR/cgienv"
161
161
162 $ PATH_INFO='/' SCRIPT_NAME='' "$PYTHON" hgweb.cgi \
162 $ PATH_INFO='/' SCRIPT_NAME='' "$PYTHON" hgweb.cgi \
163 > | grep '^[0-9]) ' # ignores HTML output
163 > | grep '^[0-9]) ' # ignores HTML output
164 1) foo imported
164 1) foo imported
165 1) bar imported
165 1) bar imported
166 2) foo uisetup
166 2) foo uisetup
167 2) bar uisetup
167 2) bar uisetup
168 3) foo extsetup
168 3) foo extsetup
169 3) bar extsetup
169 3) bar extsetup
170 4) foo reposetup
170 4) foo reposetup
171 4) bar reposetup
171 4) bar reposetup
172
172
173 (check that revset predicate foo() and bar() are available)
173 (check that revset predicate foo() and bar() are available)
174
174
175 #if msys
175 #if msys
176 $ PATH_INFO='//shortlog'
176 $ PATH_INFO='//shortlog'
177 #else
177 #else
178 $ PATH_INFO='/shortlog'
178 $ PATH_INFO='/shortlog'
179 #endif
179 #endif
180 $ export PATH_INFO
180 $ export PATH_INFO
181 $ SCRIPT_NAME='' QUERY_STRING='rev=foo() and bar()' "$PYTHON" hgweb.cgi \
181 $ SCRIPT_NAME='' QUERY_STRING='rev=foo() and bar()' "$PYTHON" hgweb.cgi \
182 > | grep '<a href="/rev/[0-9a-z]*">'
182 > | grep '<a href="/rev/[0-9a-z]*">'
183 <a href="/rev/c24b9ac61126">add file</a>
183 <a href="/rev/c24b9ac61126">add file</a>
184
184
185 $ echo 'foo = !' >> $HGRCPATH
185 $ echo 'foo = !' >> $HGRCPATH
186 $ echo 'bar = !' >> $HGRCPATH
186 $ echo 'bar = !' >> $HGRCPATH
187
187
188 Check "from __future__ import absolute_import" support for external libraries
188 Check "from __future__ import absolute_import" support for external libraries
189
189
190 (import-checker.py reports issues for some of heredoc python code
190 (import-checker.py reports issues for some of heredoc python code
191 fragments below, because import-checker.py does not know test specific
191 fragments below, because import-checker.py does not know test specific
192 package hierarchy. NO_CHECK_* should be used as a limit mark of
192 package hierarchy. NO_CHECK_* should be used as a limit mark of
193 heredoc, in order to make import-checker.py ignore them. For
193 heredoc, in order to make import-checker.py ignore them. For
194 simplicity, all python code fragments below are generated with such
194 simplicity, all python code fragments below are generated with such
195 limit mark, regardless of importing module or not.)
195 limit mark, regardless of importing module or not.)
196
196
197 #if windows
197 #if windows
198 $ PATHSEP=";"
198 $ PATHSEP=";"
199 #else
199 #else
200 $ PATHSEP=":"
200 $ PATHSEP=":"
201 #endif
201 #endif
202 $ export PATHSEP
202 $ export PATHSEP
203
203
204 $ mkdir $TESTTMP/libroot
204 $ mkdir $TESTTMP/libroot
205 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
205 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
206 $ mkdir $TESTTMP/libroot/mod
206 $ mkdir $TESTTMP/libroot/mod
207 $ touch $TESTTMP/libroot/mod/__init__.py
207 $ touch $TESTTMP/libroot/mod/__init__.py
208 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
208 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
209
209
210 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<NO_CHECK_EOF
210 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<NO_CHECK_EOF
211 > from __future__ import absolute_import, print_function
211 > from __future__ import absolute_import, print_function
212 > import ambig # should load "libroot/ambig.py"
212 > import ambig # should load "libroot/ambig.py"
213 > s = ambig.s
213 > s = ambig.s
214 > NO_CHECK_EOF
214 > NO_CHECK_EOF
215 $ cat > loadabs.py <<NO_CHECK_EOF
215 $ cat > loadabs.py <<NO_CHECK_EOF
216 > import mod.ambigabs as ambigabs
216 > import mod.ambigabs as ambigabs
217 > def extsetup():
217 > def extsetup():
218 > print('ambigabs.s=%s' % ambigabs.s, flush=True)
218 > print('ambigabs.s=%s' % ambigabs.s, flush=True)
219 > NO_CHECK_EOF
219 > NO_CHECK_EOF
220 $ $PYTHON $TESTTMP/unflush.py loadabs.py
220 $ $PYTHON $TESTTMP/unflush.py loadabs.py
221 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
221 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
222 ambigabs.s=libroot/ambig.py
222 ambigabs.s=libroot/ambig.py
223 $TESTTMP/a
223 $TESTTMP/a
224
224
225 #if no-py3k
225 #if no-py3
226 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<NO_CHECK_EOF
226 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<NO_CHECK_EOF
227 > from __future__ import print_function
227 > from __future__ import print_function
228 > import ambig # should load "libroot/mod/ambig.py"
228 > import ambig # should load "libroot/mod/ambig.py"
229 > s = ambig.s
229 > s = ambig.s
230 > NO_CHECK_EOF
230 > NO_CHECK_EOF
231 $ cat > loadrel.py <<NO_CHECK_EOF
231 $ cat > loadrel.py <<NO_CHECK_EOF
232 > import mod.ambigrel as ambigrel
232 > import mod.ambigrel as ambigrel
233 > def extsetup():
233 > def extsetup():
234 > print('ambigrel.s=%s' % ambigrel.s, flush=True)
234 > print('ambigrel.s=%s' % ambigrel.s, flush=True)
235 > NO_CHECK_EOF
235 > NO_CHECK_EOF
236 $ $PYTHON $TESTTMP/unflush.py loadrel.py
236 $ $PYTHON $TESTTMP/unflush.py loadrel.py
237 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
237 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
238 ambigrel.s=libroot/mod/ambig.py
238 ambigrel.s=libroot/mod/ambig.py
239 $TESTTMP/a
239 $TESTTMP/a
240 #endif
240 #endif
241
241
242 Check absolute/relative import of extension specific modules
242 Check absolute/relative import of extension specific modules
243
243
244 $ mkdir $TESTTMP/extroot
244 $ mkdir $TESTTMP/extroot
245 $ cat > $TESTTMP/extroot/bar.py <<NO_CHECK_EOF
245 $ cat > $TESTTMP/extroot/bar.py <<NO_CHECK_EOF
246 > s = b'this is extroot.bar'
246 > s = b'this is extroot.bar'
247 > NO_CHECK_EOF
247 > NO_CHECK_EOF
248 $ mkdir $TESTTMP/extroot/sub1
248 $ mkdir $TESTTMP/extroot/sub1
249 $ cat > $TESTTMP/extroot/sub1/__init__.py <<NO_CHECK_EOF
249 $ cat > $TESTTMP/extroot/sub1/__init__.py <<NO_CHECK_EOF
250 > s = b'this is extroot.sub1.__init__'
250 > s = b'this is extroot.sub1.__init__'
251 > NO_CHECK_EOF
251 > NO_CHECK_EOF
252 $ cat > $TESTTMP/extroot/sub1/baz.py <<NO_CHECK_EOF
252 $ cat > $TESTTMP/extroot/sub1/baz.py <<NO_CHECK_EOF
253 > s = b'this is extroot.sub1.baz'
253 > s = b'this is extroot.sub1.baz'
254 > NO_CHECK_EOF
254 > NO_CHECK_EOF
255 $ cat > $TESTTMP/extroot/__init__.py <<NO_CHECK_EOF
255 $ cat > $TESTTMP/extroot/__init__.py <<NO_CHECK_EOF
256 > from __future__ import absolute_import
256 > from __future__ import absolute_import
257 > s = b'this is extroot.__init__'
257 > s = b'this is extroot.__init__'
258 > from . import foo
258 > from . import foo
259 > def extsetup(ui):
259 > def extsetup(ui):
260 > ui.write(b'(extroot) ', foo.func(), b'\n')
260 > ui.write(b'(extroot) ', foo.func(), b'\n')
261 > ui.flush()
261 > ui.flush()
262 > NO_CHECK_EOF
262 > NO_CHECK_EOF
263
263
264 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
264 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
265 > # test absolute import
265 > # test absolute import
266 > buf = []
266 > buf = []
267 > def func():
267 > def func():
268 > # "not locals" case
268 > # "not locals" case
269 > import extroot.bar
269 > import extroot.bar
270 > buf.append(b'import extroot.bar in func(): %s' % extroot.bar.s)
270 > buf.append(b'import extroot.bar in func(): %s' % extroot.bar.s)
271 > return b'\n(extroot) '.join(buf)
271 > return b'\n(extroot) '.join(buf)
272 > # b"fromlist == ('*',)" case
272 > # b"fromlist == ('*',)" case
273 > from extroot.bar import *
273 > from extroot.bar import *
274 > buf.append(b'from extroot.bar import *: %s' % s)
274 > buf.append(b'from extroot.bar import *: %s' % s)
275 > # "not fromlist" and "if '.' in name" case
275 > # "not fromlist" and "if '.' in name" case
276 > import extroot.sub1.baz
276 > import extroot.sub1.baz
277 > buf.append(b'import extroot.sub1.baz: %s' % extroot.sub1.baz.s)
277 > buf.append(b'import extroot.sub1.baz: %s' % extroot.sub1.baz.s)
278 > # "not fromlist" and NOT "if '.' in name" case
278 > # "not fromlist" and NOT "if '.' in name" case
279 > import extroot
279 > import extroot
280 > buf.append(b'import extroot: %s' % extroot.s)
280 > buf.append(b'import extroot: %s' % extroot.s)
281 > # NOT "not fromlist" and NOT "level != -1" case
281 > # NOT "not fromlist" and NOT "level != -1" case
282 > from extroot.bar import s
282 > from extroot.bar import s
283 > buf.append(b'from extroot.bar import s: %s' % s)
283 > buf.append(b'from extroot.bar import s: %s' % s)
284 > NO_CHECK_EOF
284 > NO_CHECK_EOF
285 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root)
285 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root)
286 (extroot) from extroot.bar import *: this is extroot.bar
286 (extroot) from extroot.bar import *: this is extroot.bar
287 (extroot) import extroot.sub1.baz: this is extroot.sub1.baz
287 (extroot) import extroot.sub1.baz: this is extroot.sub1.baz
288 (extroot) import extroot: this is extroot.__init__
288 (extroot) import extroot: this is extroot.__init__
289 (extroot) from extroot.bar import s: this is extroot.bar
289 (extroot) from extroot.bar import s: this is extroot.bar
290 (extroot) import extroot.bar in func(): this is extroot.bar
290 (extroot) import extroot.bar in func(): this is extroot.bar
291 $TESTTMP/a
291 $TESTTMP/a
292
292
293 #if no-py3k
293 #if no-py3
294 $ rm "$TESTTMP"/extroot/foo.*
294 $ rm "$TESTTMP"/extroot/foo.*
295 $ rm -Rf "$TESTTMP/extroot/__pycache__"
295 $ rm -Rf "$TESTTMP/extroot/__pycache__"
296 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
296 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
297 > # test relative import
297 > # test relative import
298 > buf = []
298 > buf = []
299 > def func():
299 > def func():
300 > # "not locals" case
300 > # "not locals" case
301 > import bar
301 > import bar
302 > buf.append('import bar in func(): %s' % bar.s)
302 > buf.append('import bar in func(): %s' % bar.s)
303 > return '\n(extroot) '.join(buf)
303 > return '\n(extroot) '.join(buf)
304 > # "fromlist == ('*',)" case
304 > # "fromlist == ('*',)" case
305 > from bar import *
305 > from bar import *
306 > buf.append('from bar import *: %s' % s)
306 > buf.append('from bar import *: %s' % s)
307 > # "not fromlist" and "if '.' in name" case
307 > # "not fromlist" and "if '.' in name" case
308 > import sub1.baz
308 > import sub1.baz
309 > buf.append('import sub1.baz: %s' % sub1.baz.s)
309 > buf.append('import sub1.baz: %s' % sub1.baz.s)
310 > # "not fromlist" and NOT "if '.' in name" case
310 > # "not fromlist" and NOT "if '.' in name" case
311 > import sub1
311 > import sub1
312 > buf.append('import sub1: %s' % sub1.s)
312 > buf.append('import sub1: %s' % sub1.s)
313 > # NOT "not fromlist" and NOT "level != -1" case
313 > # NOT "not fromlist" and NOT "level != -1" case
314 > from bar import s
314 > from bar import s
315 > buf.append('from bar import s: %s' % s)
315 > buf.append('from bar import s: %s' % s)
316 > NO_CHECK_EOF
316 > NO_CHECK_EOF
317 $ hg --config extensions.extroot=$TESTTMP/extroot root
317 $ hg --config extensions.extroot=$TESTTMP/extroot root
318 (extroot) from bar import *: this is extroot.bar
318 (extroot) from bar import *: this is extroot.bar
319 (extroot) import sub1.baz: this is extroot.sub1.baz
319 (extroot) import sub1.baz: this is extroot.sub1.baz
320 (extroot) import sub1: this is extroot.sub1.__init__
320 (extroot) import sub1: this is extroot.sub1.__init__
321 (extroot) from bar import s: this is extroot.bar
321 (extroot) from bar import s: this is extroot.bar
322 (extroot) import bar in func(): this is extroot.bar
322 (extroot) import bar in func(): this is extroot.bar
323 $TESTTMP/a
323 $TESTTMP/a
324 #endif
324 #endif
325
325
326 #if demandimport
326 #if demandimport
327
327
328 Examine whether module loading is delayed until actual referring, even
328 Examine whether module loading is delayed until actual referring, even
329 though module is imported with "absolute_import" feature.
329 though module is imported with "absolute_import" feature.
330
330
331 Files below in each packages are used for described purpose:
331 Files below in each packages are used for described purpose:
332
332
333 - "called": examine whether "from MODULE import ATTR" works correctly
333 - "called": examine whether "from MODULE import ATTR" works correctly
334 - "unused": examine whether loading is delayed correctly
334 - "unused": examine whether loading is delayed correctly
335 - "used": examine whether "from PACKAGE import MODULE" works correctly
335 - "used": examine whether "from PACKAGE import MODULE" works correctly
336
336
337 Package hierarchy is needed to examine whether demand importing works
337 Package hierarchy is needed to examine whether demand importing works
338 as expected for "from SUB.PACK.AGE import MODULE".
338 as expected for "from SUB.PACK.AGE import MODULE".
339
339
340 Setup "external library" to be imported with "absolute_import"
340 Setup "external library" to be imported with "absolute_import"
341 feature.
341 feature.
342
342
343 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
343 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
344 $ touch $TESTTMP/extlibroot/__init__.py
344 $ touch $TESTTMP/extlibroot/__init__.py
345 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
345 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
346 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
346 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
347
347
348 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<NO_CHECK_EOF
348 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<NO_CHECK_EOF
349 > def func():
349 > def func():
350 > return b"this is extlibroot.lsub1.lsub2.called.func()"
350 > return b"this is extlibroot.lsub1.lsub2.called.func()"
351 > NO_CHECK_EOF
351 > NO_CHECK_EOF
352 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<NO_CHECK_EOF
352 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<NO_CHECK_EOF
353 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
353 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
354 > NO_CHECK_EOF
354 > NO_CHECK_EOF
355 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<NO_CHECK_EOF
355 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<NO_CHECK_EOF
356 > detail = b"this is extlibroot.lsub1.lsub2.used"
356 > detail = b"this is extlibroot.lsub1.lsub2.used"
357 > NO_CHECK_EOF
357 > NO_CHECK_EOF
358
358
359 Setup sub-package of "external library", which causes instantiation of
359 Setup sub-package of "external library", which causes instantiation of
360 demandmod in "recurse down the module chain" code path. Relative
360 demandmod in "recurse down the module chain" code path. Relative
361 importing with "absolute_import" feature isn't tested, because "level
361 importing with "absolute_import" feature isn't tested, because "level
362 >=1 " doesn't cause instantiation of demandmod.
362 >=1 " doesn't cause instantiation of demandmod.
363
363
364 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
364 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
365 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<NO_CHECK_EOF
365 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<NO_CHECK_EOF
366 > detail = b"this is extlibroot.recursedown.abs.used"
366 > detail = b"this is extlibroot.recursedown.abs.used"
367 > NO_CHECK_EOF
367 > NO_CHECK_EOF
368 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<NO_CHECK_EOF
368 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<NO_CHECK_EOF
369 > from __future__ import absolute_import
369 > from __future__ import absolute_import
370 > from extlibroot.recursedown.abs.used import detail
370 > from extlibroot.recursedown.abs.used import detail
371 > NO_CHECK_EOF
371 > NO_CHECK_EOF
372
372
373 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
373 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
374 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<NO_CHECK_EOF
374 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<NO_CHECK_EOF
375 > detail = b"this is extlibroot.recursedown.legacy.used"
375 > detail = b"this is extlibroot.recursedown.legacy.used"
376 > NO_CHECK_EOF
376 > NO_CHECK_EOF
377 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<NO_CHECK_EOF
377 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<NO_CHECK_EOF
378 > # legacy style (level == -1) import
378 > # legacy style (level == -1) import
379 > from extlibroot.recursedown.legacy.used import detail
379 > from extlibroot.recursedown.legacy.used import detail
380 > NO_CHECK_EOF
380 > NO_CHECK_EOF
381
381
382 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<NO_CHECK_EOF
382 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<NO_CHECK_EOF
383 > from __future__ import absolute_import
383 > from __future__ import absolute_import
384 > from extlibroot.recursedown.abs import detail as absdetail
384 > from extlibroot.recursedown.abs import detail as absdetail
385 > from .legacy import detail as legacydetail
385 > from .legacy import detail as legacydetail
386 > NO_CHECK_EOF
386 > NO_CHECK_EOF
387
387
388 Setup package that re-exports an attribute of its submodule as the same
388 Setup package that re-exports an attribute of its submodule as the same
389 name. This leaves 'shadowing.used' pointing to 'used.detail', but still
389 name. This leaves 'shadowing.used' pointing to 'used.detail', but still
390 the submodule 'used' should be somehow accessible. (issue5617)
390 the submodule 'used' should be somehow accessible. (issue5617)
391
391
392 $ mkdir -p $TESTTMP/extlibroot/shadowing
392 $ mkdir -p $TESTTMP/extlibroot/shadowing
393 $ cat > $TESTTMP/extlibroot/shadowing/used.py <<NO_CHECK_EOF
393 $ cat > $TESTTMP/extlibroot/shadowing/used.py <<NO_CHECK_EOF
394 > detail = b"this is extlibroot.shadowing.used"
394 > detail = b"this is extlibroot.shadowing.used"
395 > NO_CHECK_EOF
395 > NO_CHECK_EOF
396 $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<NO_CHECK_EOF
396 $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<NO_CHECK_EOF
397 > from __future__ import absolute_import
397 > from __future__ import absolute_import
398 > from extlibroot.shadowing.used import detail
398 > from extlibroot.shadowing.used import detail
399 > NO_CHECK_EOF
399 > NO_CHECK_EOF
400 $ cat > $TESTTMP/extlibroot/shadowing/__init__.py <<NO_CHECK_EOF
400 $ cat > $TESTTMP/extlibroot/shadowing/__init__.py <<NO_CHECK_EOF
401 > from __future__ import absolute_import
401 > from __future__ import absolute_import
402 > from .used import detail as used
402 > from .used import detail as used
403 > NO_CHECK_EOF
403 > NO_CHECK_EOF
404
404
405 Setup extension local modules to be imported with "absolute_import"
405 Setup extension local modules to be imported with "absolute_import"
406 feature.
406 feature.
407
407
408 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
408 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
409 $ touch $TESTTMP/absextroot/xsub1/__init__.py
409 $ touch $TESTTMP/absextroot/xsub1/__init__.py
410 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
410 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
411
411
412 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<NO_CHECK_EOF
412 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<NO_CHECK_EOF
413 > def func():
413 > def func():
414 > return b"this is absextroot.xsub1.xsub2.called.func()"
414 > return b"this is absextroot.xsub1.xsub2.called.func()"
415 > NO_CHECK_EOF
415 > NO_CHECK_EOF
416 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<NO_CHECK_EOF
416 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<NO_CHECK_EOF
417 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
417 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
418 > NO_CHECK_EOF
418 > NO_CHECK_EOF
419 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<NO_CHECK_EOF
419 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<NO_CHECK_EOF
420 > detail = b"this is absextroot.xsub1.xsub2.used"
420 > detail = b"this is absextroot.xsub1.xsub2.used"
421 > NO_CHECK_EOF
421 > NO_CHECK_EOF
422
422
423 Setup extension local modules to examine whether demand importing
423 Setup extension local modules to examine whether demand importing
424 works as expected in "level > 1" case.
424 works as expected in "level > 1" case.
425
425
426 $ cat > $TESTTMP/absextroot/relimportee.py <<NO_CHECK_EOF
426 $ cat > $TESTTMP/absextroot/relimportee.py <<NO_CHECK_EOF
427 > detail = b"this is absextroot.relimportee"
427 > detail = b"this is absextroot.relimportee"
428 > NO_CHECK_EOF
428 > NO_CHECK_EOF
429 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<NO_CHECK_EOF
429 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<NO_CHECK_EOF
430 > from __future__ import absolute_import
430 > from __future__ import absolute_import
431 > from mercurial import pycompat
431 > from mercurial import pycompat
432 > from ... import relimportee
432 > from ... import relimportee
433 > detail = b"this relimporter imports %r" % (
433 > detail = b"this relimporter imports %r" % (
434 > pycompat.bytestr(relimportee.detail))
434 > pycompat.bytestr(relimportee.detail))
435 > NO_CHECK_EOF
435 > NO_CHECK_EOF
436
436
437 Setup modules, which actually import extension local modules at
437 Setup modules, which actually import extension local modules at
438 runtime.
438 runtime.
439
439
440 $ cat > $TESTTMP/absextroot/absolute.py << NO_CHECK_EOF
440 $ cat > $TESTTMP/absextroot/absolute.py << NO_CHECK_EOF
441 > from __future__ import absolute_import
441 > from __future__ import absolute_import
442 >
442 >
443 > # import extension local modules absolutely (level = 0)
443 > # import extension local modules absolutely (level = 0)
444 > from absextroot.xsub1.xsub2 import used, unused
444 > from absextroot.xsub1.xsub2 import used, unused
445 > from absextroot.xsub1.xsub2.called import func
445 > from absextroot.xsub1.xsub2.called import func
446 >
446 >
447 > def getresult():
447 > def getresult():
448 > result = []
448 > result = []
449 > result.append(used.detail)
449 > result.append(used.detail)
450 > result.append(func())
450 > result.append(func())
451 > return result
451 > return result
452 > NO_CHECK_EOF
452 > NO_CHECK_EOF
453
453
454 $ cat > $TESTTMP/absextroot/relative.py << NO_CHECK_EOF
454 $ cat > $TESTTMP/absextroot/relative.py << NO_CHECK_EOF
455 > from __future__ import absolute_import
455 > from __future__ import absolute_import
456 >
456 >
457 > # import extension local modules relatively (level == 1)
457 > # import extension local modules relatively (level == 1)
458 > from .xsub1.xsub2 import used, unused
458 > from .xsub1.xsub2 import used, unused
459 > from .xsub1.xsub2.called import func
459 > from .xsub1.xsub2.called import func
460 >
460 >
461 > # import a module, which implies "importing with level > 1"
461 > # import a module, which implies "importing with level > 1"
462 > from .xsub1.xsub2 import relimporter
462 > from .xsub1.xsub2 import relimporter
463 >
463 >
464 > def getresult():
464 > def getresult():
465 > result = []
465 > result = []
466 > result.append(used.detail)
466 > result.append(used.detail)
467 > result.append(func())
467 > result.append(func())
468 > result.append(relimporter.detail)
468 > result.append(relimporter.detail)
469 > return result
469 > return result
470 > NO_CHECK_EOF
470 > NO_CHECK_EOF
471
471
472 Setup main procedure of extension.
472 Setup main procedure of extension.
473
473
474 $ cat > $TESTTMP/absextroot/__init__.py <<NO_CHECK_EOF
474 $ cat > $TESTTMP/absextroot/__init__.py <<NO_CHECK_EOF
475 > from __future__ import absolute_import
475 > from __future__ import absolute_import
476 > from mercurial import registrar
476 > from mercurial import registrar
477 > cmdtable = {}
477 > cmdtable = {}
478 > command = registrar.command(cmdtable)
478 > command = registrar.command(cmdtable)
479 >
479 >
480 > # "absolute" and "relative" shouldn't be imported before actual
480 > # "absolute" and "relative" shouldn't be imported before actual
481 > # command execution, because (1) they import same modules, and (2)
481 > # command execution, because (1) they import same modules, and (2)
482 > # preceding import (= instantiate "demandmod" object instead of
482 > # preceding import (= instantiate "demandmod" object instead of
483 > # real "module" object) might hide problem of succeeding import.
483 > # real "module" object) might hide problem of succeeding import.
484 >
484 >
485 > @command(b'showabsolute', [], norepo=True)
485 > @command(b'showabsolute', [], norepo=True)
486 > def showabsolute(ui, *args, **opts):
486 > def showabsolute(ui, *args, **opts):
487 > from absextroot import absolute
487 > from absextroot import absolute
488 > ui.write(b'ABS: %s\n' % b'\nABS: '.join(absolute.getresult()))
488 > ui.write(b'ABS: %s\n' % b'\nABS: '.join(absolute.getresult()))
489 >
489 >
490 > @command(b'showrelative', [], norepo=True)
490 > @command(b'showrelative', [], norepo=True)
491 > def showrelative(ui, *args, **opts):
491 > def showrelative(ui, *args, **opts):
492 > from . import relative
492 > from . import relative
493 > ui.write(b'REL: %s\n' % b'\nREL: '.join(relative.getresult()))
493 > ui.write(b'REL: %s\n' % b'\nREL: '.join(relative.getresult()))
494 >
494 >
495 > # import modules from external library
495 > # import modules from external library
496 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
496 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
497 > from extlibroot.lsub1.lsub2.called import func as lfunc
497 > from extlibroot.lsub1.lsub2.called import func as lfunc
498 > from extlibroot.recursedown import absdetail, legacydetail
498 > from extlibroot.recursedown import absdetail, legacydetail
499 > from extlibroot.shadowing import proxied
499 > from extlibroot.shadowing import proxied
500 >
500 >
501 > def uisetup(ui):
501 > def uisetup(ui):
502 > result = []
502 > result = []
503 > result.append(lused.detail)
503 > result.append(lused.detail)
504 > result.append(lfunc())
504 > result.append(lfunc())
505 > result.append(absdetail)
505 > result.append(absdetail)
506 > result.append(legacydetail)
506 > result.append(legacydetail)
507 > result.append(proxied.detail)
507 > result.append(proxied.detail)
508 > ui.write(b'LIB: %s\n' % b'\nLIB: '.join(result))
508 > ui.write(b'LIB: %s\n' % b'\nLIB: '.join(result))
509 > NO_CHECK_EOF
509 > NO_CHECK_EOF
510
510
511 Examine module importing.
511 Examine module importing.
512
512
513 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
513 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
514 LIB: this is extlibroot.lsub1.lsub2.used
514 LIB: this is extlibroot.lsub1.lsub2.used
515 LIB: this is extlibroot.lsub1.lsub2.called.func()
515 LIB: this is extlibroot.lsub1.lsub2.called.func()
516 LIB: this is extlibroot.recursedown.abs.used
516 LIB: this is extlibroot.recursedown.abs.used
517 LIB: this is extlibroot.recursedown.legacy.used
517 LIB: this is extlibroot.recursedown.legacy.used
518 LIB: this is extlibroot.shadowing.used
518 LIB: this is extlibroot.shadowing.used
519 ABS: this is absextroot.xsub1.xsub2.used
519 ABS: this is absextroot.xsub1.xsub2.used
520 ABS: this is absextroot.xsub1.xsub2.called.func()
520 ABS: this is absextroot.xsub1.xsub2.called.func()
521
521
522 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
522 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
523 LIB: this is extlibroot.lsub1.lsub2.used
523 LIB: this is extlibroot.lsub1.lsub2.used
524 LIB: this is extlibroot.lsub1.lsub2.called.func()
524 LIB: this is extlibroot.lsub1.lsub2.called.func()
525 LIB: this is extlibroot.recursedown.abs.used
525 LIB: this is extlibroot.recursedown.abs.used
526 LIB: this is extlibroot.recursedown.legacy.used
526 LIB: this is extlibroot.recursedown.legacy.used
527 LIB: this is extlibroot.shadowing.used
527 LIB: this is extlibroot.shadowing.used
528 REL: this is absextroot.xsub1.xsub2.used
528 REL: this is absextroot.xsub1.xsub2.used
529 REL: this is absextroot.xsub1.xsub2.called.func()
529 REL: this is absextroot.xsub1.xsub2.called.func()
530 REL: this relimporter imports 'this is absextroot.relimportee'
530 REL: this relimporter imports 'this is absextroot.relimportee'
531
531
532 Examine whether sub-module is imported relatively as expected.
532 Examine whether sub-module is imported relatively as expected.
533
533
534 See also issue5208 for detail about example case on Python 3.x.
534 See also issue5208 for detail about example case on Python 3.x.
535
535
536 $ f -q $TESTTMP/extlibroot/lsub1/lsub2/notexist.py
536 $ f -q $TESTTMP/extlibroot/lsub1/lsub2/notexist.py
537 $TESTTMP/extlibroot/lsub1/lsub2/notexist.py: file not found
537 $TESTTMP/extlibroot/lsub1/lsub2/notexist.py: file not found
538
538
539 $ cat > $TESTTMP/notexist.py <<NO_CHECK_EOF
539 $ cat > $TESTTMP/notexist.py <<NO_CHECK_EOF
540 > text = 'notexist.py at root is loaded unintentionally\n'
540 > text = 'notexist.py at root is loaded unintentionally\n'
541 > NO_CHECK_EOF
541 > NO_CHECK_EOF
542
542
543 $ cat > $TESTTMP/checkrelativity.py <<NO_CHECK_EOF
543 $ cat > $TESTTMP/checkrelativity.py <<NO_CHECK_EOF
544 > from mercurial import registrar
544 > from mercurial import registrar
545 > cmdtable = {}
545 > cmdtable = {}
546 > command = registrar.command(cmdtable)
546 > command = registrar.command(cmdtable)
547 >
547 >
548 > # demand import avoids failure of importing notexist here
548 > # demand import avoids failure of importing notexist here
549 > import extlibroot.lsub1.lsub2.notexist
549 > import extlibroot.lsub1.lsub2.notexist
550 >
550 >
551 > @command(b'checkrelativity', [], norepo=True)
551 > @command(b'checkrelativity', [], norepo=True)
552 > def checkrelativity(ui, *args, **opts):
552 > def checkrelativity(ui, *args, **opts):
553 > try:
553 > try:
554 > ui.write(extlibroot.lsub1.lsub2.notexist.text)
554 > ui.write(extlibroot.lsub1.lsub2.notexist.text)
555 > return 1 # unintentional success
555 > return 1 # unintentional success
556 > except ImportError:
556 > except ImportError:
557 > pass # intentional failure
557 > pass # intentional failure
558 > NO_CHECK_EOF
558 > NO_CHECK_EOF
559
559
560 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity)
560 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity)
561
561
562 #endif
562 #endif
563
563
564 (Here, module importing tests are finished. Therefore, use other than
564 (Here, module importing tests are finished. Therefore, use other than
565 NO_CHECK_* limit mark for heredoc python files, in order to apply
565 NO_CHECK_* limit mark for heredoc python files, in order to apply
566 import-checker.py or so on their contents)
566 import-checker.py or so on their contents)
567
567
568 Make sure a broken uisetup doesn't globally break hg:
568 Make sure a broken uisetup doesn't globally break hg:
569 $ cat > $TESTTMP/baduisetup.py <<EOF
569 $ cat > $TESTTMP/baduisetup.py <<EOF
570 > def uisetup(ui):
570 > def uisetup(ui):
571 > 1/0
571 > 1/0
572 > EOF
572 > EOF
573
573
574 Even though the extension fails during uisetup, hg is still basically usable:
574 Even though the extension fails during uisetup, hg is still basically usable:
575 $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
575 $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
576 Traceback (most recent call last):
576 Traceback (most recent call last):
577 File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
577 File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
578 uisetup(ui)
578 uisetup(ui)
579 File "$TESTTMP/baduisetup.py", line 2, in uisetup
579 File "$TESTTMP/baduisetup.py", line 2, in uisetup
580 1/0
580 1/0
581 ZeroDivisionError: * by zero (glob)
581 ZeroDivisionError: * by zero (glob)
582 *** failed to set up extension baduisetup: * by zero (glob)
582 *** failed to set up extension baduisetup: * by zero (glob)
583 Mercurial Distributed SCM (version *) (glob)
583 Mercurial Distributed SCM (version *) (glob)
584 (see https://mercurial-scm.org for more information)
584 (see https://mercurial-scm.org for more information)
585
585
586 Copyright (C) 2005-* Matt Mackall and others (glob)
586 Copyright (C) 2005-* Matt Mackall and others (glob)
587 This is free software; see the source for copying conditions. There is NO
587 This is free software; see the source for copying conditions. There is NO
588 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
588 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
589
589
590 $ cd ..
590 $ cd ..
591
591
592 hide outer repo
592 hide outer repo
593 $ hg init
593 $ hg init
594
594
595 $ cat > empty.py <<EOF
595 $ cat > empty.py <<EOF
596 > '''empty cmdtable
596 > '''empty cmdtable
597 > '''
597 > '''
598 > cmdtable = {}
598 > cmdtable = {}
599 > EOF
599 > EOF
600 $ emptypath=`pwd`/empty.py
600 $ emptypath=`pwd`/empty.py
601 $ echo "empty = $emptypath" >> $HGRCPATH
601 $ echo "empty = $emptypath" >> $HGRCPATH
602 $ hg help empty
602 $ hg help empty
603 empty extension - empty cmdtable
603 empty extension - empty cmdtable
604
604
605 no commands defined
605 no commands defined
606
606
607
607
608 $ echo 'empty = !' >> $HGRCPATH
608 $ echo 'empty = !' >> $HGRCPATH
609
609
610 $ cat > debugextension.py <<EOF
610 $ cat > debugextension.py <<EOF
611 > '''only debugcommands
611 > '''only debugcommands
612 > '''
612 > '''
613 > from mercurial import registrar
613 > from mercurial import registrar
614 > cmdtable = {}
614 > cmdtable = {}
615 > command = registrar.command(cmdtable)
615 > command = registrar.command(cmdtable)
616 > @command(b'debugfoobar', [], b'hg debugfoobar')
616 > @command(b'debugfoobar', [], b'hg debugfoobar')
617 > def debugfoobar(ui, repo, *args, **opts):
617 > def debugfoobar(ui, repo, *args, **opts):
618 > "yet another debug command"
618 > "yet another debug command"
619 > pass
619 > pass
620 > @command(b'foo', [], b'hg foo')
620 > @command(b'foo', [], b'hg foo')
621 > def foo(ui, repo, *args, **opts):
621 > def foo(ui, repo, *args, **opts):
622 > """yet another foo command
622 > """yet another foo command
623 > This command has been DEPRECATED since forever.
623 > This command has been DEPRECATED since forever.
624 > """
624 > """
625 > pass
625 > pass
626 > EOF
626 > EOF
627 $ debugpath=`pwd`/debugextension.py
627 $ debugpath=`pwd`/debugextension.py
628 $ echo "debugextension = $debugpath" >> $HGRCPATH
628 $ echo "debugextension = $debugpath" >> $HGRCPATH
629
629
630 $ hg help debugextension
630 $ hg help debugextension
631 hg debugextensions
631 hg debugextensions
632
632
633 show information about active extensions
633 show information about active extensions
634
634
635 options:
635 options:
636
636
637 -T --template TEMPLATE display with template
637 -T --template TEMPLATE display with template
638
638
639 (some details hidden, use --verbose to show complete help)
639 (some details hidden, use --verbose to show complete help)
640
640
641
641
642 $ hg --verbose help debugextension
642 $ hg --verbose help debugextension
643 hg debugextensions
643 hg debugextensions
644
644
645 show information about active extensions
645 show information about active extensions
646
646
647 options:
647 options:
648
648
649 -T --template TEMPLATE display with template
649 -T --template TEMPLATE display with template
650
650
651 global options ([+] can be repeated):
651 global options ([+] can be repeated):
652
652
653 -R --repository REPO repository root directory or name of overlay bundle
653 -R --repository REPO repository root directory or name of overlay bundle
654 file
654 file
655 --cwd DIR change working directory
655 --cwd DIR change working directory
656 -y --noninteractive do not prompt, automatically pick the first choice for
656 -y --noninteractive do not prompt, automatically pick the first choice for
657 all prompts
657 all prompts
658 -q --quiet suppress output
658 -q --quiet suppress output
659 -v --verbose enable additional output
659 -v --verbose enable additional output
660 --color TYPE when to colorize (boolean, always, auto, never, or
660 --color TYPE when to colorize (boolean, always, auto, never, or
661 debug)
661 debug)
662 --config CONFIG [+] set/override config option (use 'section.name=value')
662 --config CONFIG [+] set/override config option (use 'section.name=value')
663 --debug enable debugging output
663 --debug enable debugging output
664 --debugger start debugger
664 --debugger start debugger
665 --encoding ENCODE set the charset encoding (default: ascii)
665 --encoding ENCODE set the charset encoding (default: ascii)
666 --encodingmode MODE set the charset encoding mode (default: strict)
666 --encodingmode MODE set the charset encoding mode (default: strict)
667 --traceback always print a traceback on exception
667 --traceback always print a traceback on exception
668 --time time how long the command takes
668 --time time how long the command takes
669 --profile print command execution profile
669 --profile print command execution profile
670 --version output version information and exit
670 --version output version information and exit
671 -h --help display help and exit
671 -h --help display help and exit
672 --hidden consider hidden changesets
672 --hidden consider hidden changesets
673 --pager TYPE when to paginate (boolean, always, auto, or never)
673 --pager TYPE when to paginate (boolean, always, auto, or never)
674 (default: auto)
674 (default: auto)
675
675
676
676
677
677
678
678
679
679
680
680
681 $ hg --debug help debugextension
681 $ hg --debug help debugextension
682 hg debugextensions
682 hg debugextensions
683
683
684 show information about active extensions
684 show information about active extensions
685
685
686 options:
686 options:
687
687
688 -T --template TEMPLATE display with template
688 -T --template TEMPLATE display with template
689
689
690 global options ([+] can be repeated):
690 global options ([+] can be repeated):
691
691
692 -R --repository REPO repository root directory or name of overlay bundle
692 -R --repository REPO repository root directory or name of overlay bundle
693 file
693 file
694 --cwd DIR change working directory
694 --cwd DIR change working directory
695 -y --noninteractive do not prompt, automatically pick the first choice for
695 -y --noninteractive do not prompt, automatically pick the first choice for
696 all prompts
696 all prompts
697 -q --quiet suppress output
697 -q --quiet suppress output
698 -v --verbose enable additional output
698 -v --verbose enable additional output
699 --color TYPE when to colorize (boolean, always, auto, never, or
699 --color TYPE when to colorize (boolean, always, auto, never, or
700 debug)
700 debug)
701 --config CONFIG [+] set/override config option (use 'section.name=value')
701 --config CONFIG [+] set/override config option (use 'section.name=value')
702 --debug enable debugging output
702 --debug enable debugging output
703 --debugger start debugger
703 --debugger start debugger
704 --encoding ENCODE set the charset encoding (default: ascii)
704 --encoding ENCODE set the charset encoding (default: ascii)
705 --encodingmode MODE set the charset encoding mode (default: strict)
705 --encodingmode MODE set the charset encoding mode (default: strict)
706 --traceback always print a traceback on exception
706 --traceback always print a traceback on exception
707 --time time how long the command takes
707 --time time how long the command takes
708 --profile print command execution profile
708 --profile print command execution profile
709 --version output version information and exit
709 --version output version information and exit
710 -h --help display help and exit
710 -h --help display help and exit
711 --hidden consider hidden changesets
711 --hidden consider hidden changesets
712 --pager TYPE when to paginate (boolean, always, auto, or never)
712 --pager TYPE when to paginate (boolean, always, auto, or never)
713 (default: auto)
713 (default: auto)
714
714
715
715
716
716
717
717
718
718
719 $ echo 'debugextension = !' >> $HGRCPATH
719 $ echo 'debugextension = !' >> $HGRCPATH
720
720
721 Asking for help about a deprecated extension should do something useful:
721 Asking for help about a deprecated extension should do something useful:
722
722
723 $ hg help glog
723 $ hg help glog
724 'glog' is provided by the following extension:
724 'glog' is provided by the following extension:
725
725
726 graphlog command to view revision graphs from a shell (DEPRECATED)
726 graphlog command to view revision graphs from a shell (DEPRECATED)
727
727
728 (use 'hg help extensions' for information on enabling extensions)
728 (use 'hg help extensions' for information on enabling extensions)
729
729
730 Extension module help vs command help:
730 Extension module help vs command help:
731
731
732 $ echo 'extdiff =' >> $HGRCPATH
732 $ echo 'extdiff =' >> $HGRCPATH
733 $ hg help extdiff
733 $ hg help extdiff
734 hg extdiff [OPT]... [FILE]...
734 hg extdiff [OPT]... [FILE]...
735
735
736 use external program to diff repository (or selected files)
736 use external program to diff repository (or selected files)
737
737
738 Show differences between revisions for the specified files, using an
738 Show differences between revisions for the specified files, using an
739 external program. The default program used is diff, with default options
739 external program. The default program used is diff, with default options
740 "-Npru".
740 "-Npru".
741
741
742 To select a different program, use the -p/--program option. The program
742 To select a different program, use the -p/--program option. The program
743 will be passed the names of two directories to compare. To pass additional
743 will be passed the names of two directories to compare. To pass additional
744 options to the program, use -o/--option. These will be passed before the
744 options to the program, use -o/--option. These will be passed before the
745 names of the directories to compare.
745 names of the directories to compare.
746
746
747 When two revision arguments are given, then changes are shown between
747 When two revision arguments are given, then changes are shown between
748 those revisions. If only one revision is specified then that revision is
748 those revisions. If only one revision is specified then that revision is
749 compared to the working directory, and, when no revisions are specified,
749 compared to the working directory, and, when no revisions are specified,
750 the working directory files are compared to its parent.
750 the working directory files are compared to its parent.
751
751
752 (use 'hg help -e extdiff' to show help for the extdiff extension)
752 (use 'hg help -e extdiff' to show help for the extdiff extension)
753
753
754 options ([+] can be repeated):
754 options ([+] can be repeated):
755
755
756 -p --program CMD comparison program to run
756 -p --program CMD comparison program to run
757 -o --option OPT [+] pass option to comparison program
757 -o --option OPT [+] pass option to comparison program
758 -r --rev REV [+] revision
758 -r --rev REV [+] revision
759 -c --change REV change made by revision
759 -c --change REV change made by revision
760 --patch compare patches for two revisions
760 --patch compare patches for two revisions
761 -I --include PATTERN [+] include names matching the given patterns
761 -I --include PATTERN [+] include names matching the given patterns
762 -X --exclude PATTERN [+] exclude names matching the given patterns
762 -X --exclude PATTERN [+] exclude names matching the given patterns
763 -S --subrepos recurse into subrepositories
763 -S --subrepos recurse into subrepositories
764
764
765 (some details hidden, use --verbose to show complete help)
765 (some details hidden, use --verbose to show complete help)
766
766
767
767
768
768
769
769
770
770
771
771
772
772
773
773
774
774
775
775
776 $ hg help --extension extdiff
776 $ hg help --extension extdiff
777 extdiff extension - command to allow external programs to compare revisions
777 extdiff extension - command to allow external programs to compare revisions
778
778
779 The extdiff Mercurial extension allows you to use external programs to compare
779 The extdiff Mercurial extension allows you to use external programs to compare
780 revisions, or revision with working directory. The external diff programs are
780 revisions, or revision with working directory. The external diff programs are
781 called with a configurable set of options and two non-option arguments: paths
781 called with a configurable set of options and two non-option arguments: paths
782 to directories containing snapshots of files to compare.
782 to directories containing snapshots of files to compare.
783
783
784 If there is more than one file being compared and the "child" revision is the
784 If there is more than one file being compared and the "child" revision is the
785 working directory, any modifications made in the external diff program will be
785 working directory, any modifications made in the external diff program will be
786 copied back to the working directory from the temporary directory.
786 copied back to the working directory from the temporary directory.
787
787
788 The extdiff extension also allows you to configure new diff commands, so you
788 The extdiff extension also allows you to configure new diff commands, so you
789 do not need to type 'hg extdiff -p kdiff3' always.
789 do not need to type 'hg extdiff -p kdiff3' always.
790
790
791 [extdiff]
791 [extdiff]
792 # add new command that runs GNU diff(1) in 'context diff' mode
792 # add new command that runs GNU diff(1) in 'context diff' mode
793 cdiff = gdiff -Nprc5
793 cdiff = gdiff -Nprc5
794 ## or the old way:
794 ## or the old way:
795 #cmd.cdiff = gdiff
795 #cmd.cdiff = gdiff
796 #opts.cdiff = -Nprc5
796 #opts.cdiff = -Nprc5
797
797
798 # add new command called meld, runs meld (no need to name twice). If
798 # add new command called meld, runs meld (no need to name twice). If
799 # the meld executable is not available, the meld tool in [merge-tools]
799 # the meld executable is not available, the meld tool in [merge-tools]
800 # will be used, if available
800 # will be used, if available
801 meld =
801 meld =
802
802
803 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
803 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
804 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
804 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
805 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
805 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
806 # your .vimrc
806 # your .vimrc
807 vimdiff = gvim -f "+next" \
807 vimdiff = gvim -f "+next" \
808 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
808 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
809
809
810 Tool arguments can include variables that are expanded at runtime:
810 Tool arguments can include variables that are expanded at runtime:
811
811
812 $parent1, $plabel1 - filename, descriptive label of first parent
812 $parent1, $plabel1 - filename, descriptive label of first parent
813 $child, $clabel - filename, descriptive label of child revision
813 $child, $clabel - filename, descriptive label of child revision
814 $parent2, $plabel2 - filename, descriptive label of second parent
814 $parent2, $plabel2 - filename, descriptive label of second parent
815 $root - repository root
815 $root - repository root
816 $parent is an alias for $parent1.
816 $parent is an alias for $parent1.
817
817
818 The extdiff extension will look in your [diff-tools] and [merge-tools]
818 The extdiff extension will look in your [diff-tools] and [merge-tools]
819 sections for diff tool arguments, when none are specified in [extdiff].
819 sections for diff tool arguments, when none are specified in [extdiff].
820
820
821 [extdiff]
821 [extdiff]
822 kdiff3 =
822 kdiff3 =
823
823
824 [diff-tools]
824 [diff-tools]
825 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
825 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
826
826
827 You can use -I/-X and list of file or directory names like normal 'hg diff'
827 You can use -I/-X and list of file or directory names like normal 'hg diff'
828 command. The extdiff extension makes snapshots of only needed files, so
828 command. The extdiff extension makes snapshots of only needed files, so
829 running the external diff program will actually be pretty fast (at least
829 running the external diff program will actually be pretty fast (at least
830 faster than having to compare the entire tree).
830 faster than having to compare the entire tree).
831
831
832 list of commands:
832 list of commands:
833
833
834 extdiff use external program to diff repository (or selected files)
834 extdiff use external program to diff repository (or selected files)
835
835
836 (use 'hg help -v -e extdiff' to show built-in aliases and global options)
836 (use 'hg help -v -e extdiff' to show built-in aliases and global options)
837
837
838
838
839
839
840
840
841
841
842
842
843
843
844
844
845
845
846
846
847
847
848
848
849
849
850
850
851
851
852
852
853 $ echo 'extdiff = !' >> $HGRCPATH
853 $ echo 'extdiff = !' >> $HGRCPATH
854
854
855 Test help topic with same name as extension
855 Test help topic with same name as extension
856
856
857 $ cat > multirevs.py <<EOF
857 $ cat > multirevs.py <<EOF
858 > from mercurial import commands, registrar
858 > from mercurial import commands, registrar
859 > cmdtable = {}
859 > cmdtable = {}
860 > command = registrar.command(cmdtable)
860 > command = registrar.command(cmdtable)
861 > """multirevs extension
861 > """multirevs extension
862 > Big multi-line module docstring."""
862 > Big multi-line module docstring."""
863 > @command(b'multirevs', [], b'ARG', norepo=True)
863 > @command(b'multirevs', [], b'ARG', norepo=True)
864 > def multirevs(ui, repo, arg, *args, **opts):
864 > def multirevs(ui, repo, arg, *args, **opts):
865 > """multirevs command"""
865 > """multirevs command"""
866 > pass
866 > pass
867 > EOF
867 > EOF
868 $ echo "multirevs = multirevs.py" >> $HGRCPATH
868 $ echo "multirevs = multirevs.py" >> $HGRCPATH
869
869
870 $ hg help multirevs | tail
870 $ hg help multirevs | tail
871 used):
871 used):
872
872
873 hg update :@
873 hg update :@
874
874
875 - Show diff between tags 1.3 and 1.5 (this works because the first and the
875 - Show diff between tags 1.3 and 1.5 (this works because the first and the
876 last revisions of the revset are used):
876 last revisions of the revset are used):
877
877
878 hg diff -r 1.3::1.5
878 hg diff -r 1.3::1.5
879
879
880 use 'hg help -c multirevs' to see help for the multirevs command
880 use 'hg help -c multirevs' to see help for the multirevs command
881
881
882
882
883
883
884
884
885
885
886
886
887 $ hg help -c multirevs
887 $ hg help -c multirevs
888 hg multirevs ARG
888 hg multirevs ARG
889
889
890 multirevs command
890 multirevs command
891
891
892 (some details hidden, use --verbose to show complete help)
892 (some details hidden, use --verbose to show complete help)
893
893
894
894
895
895
896 $ hg multirevs
896 $ hg multirevs
897 hg multirevs: invalid arguments
897 hg multirevs: invalid arguments
898 hg multirevs ARG
898 hg multirevs ARG
899
899
900 multirevs command
900 multirevs command
901
901
902 (use 'hg multirevs -h' to show more help)
902 (use 'hg multirevs -h' to show more help)
903 [255]
903 [255]
904
904
905
905
906
906
907 $ echo "multirevs = !" >> $HGRCPATH
907 $ echo "multirevs = !" >> $HGRCPATH
908
908
909 Issue811: Problem loading extensions twice (by site and by user)
909 Issue811: Problem loading extensions twice (by site and by user)
910
910
911 $ cat <<EOF >> $HGRCPATH
911 $ cat <<EOF >> $HGRCPATH
912 > mq =
912 > mq =
913 > strip =
913 > strip =
914 > hgext.mq =
914 > hgext.mq =
915 > hgext/mq =
915 > hgext/mq =
916 > EOF
916 > EOF
917
917
918 Show extensions:
918 Show extensions:
919 (note that mq force load strip, also checking it's not loaded twice)
919 (note that mq force load strip, also checking it's not loaded twice)
920
920
921 #if no-extraextensions
921 #if no-extraextensions
922 $ hg debugextensions
922 $ hg debugextensions
923 mq
923 mq
924 strip
924 strip
925 #endif
925 #endif
926
926
927 For extensions, which name matches one of its commands, help
927 For extensions, which name matches one of its commands, help
928 message should ask '-v -e' to get list of built-in aliases
928 message should ask '-v -e' to get list of built-in aliases
929 along with extension help itself
929 along with extension help itself
930
930
931 $ mkdir $TESTTMP/d
931 $ mkdir $TESTTMP/d
932 $ cat > $TESTTMP/d/dodo.py <<EOF
932 $ cat > $TESTTMP/d/dodo.py <<EOF
933 > """
933 > """
934 > This is an awesome 'dodo' extension. It does nothing and
934 > This is an awesome 'dodo' extension. It does nothing and
935 > writes 'Foo foo'
935 > writes 'Foo foo'
936 > """
936 > """
937 > from mercurial import commands, registrar
937 > from mercurial import commands, registrar
938 > cmdtable = {}
938 > cmdtable = {}
939 > command = registrar.command(cmdtable)
939 > command = registrar.command(cmdtable)
940 > @command(b'dodo', [], b'hg dodo')
940 > @command(b'dodo', [], b'hg dodo')
941 > def dodo(ui, *args, **kwargs):
941 > def dodo(ui, *args, **kwargs):
942 > """Does nothing"""
942 > """Does nothing"""
943 > ui.write(b"I do nothing. Yay\\n")
943 > ui.write(b"I do nothing. Yay\\n")
944 > @command(b'foofoo', [], b'hg foofoo')
944 > @command(b'foofoo', [], b'hg foofoo')
945 > def foofoo(ui, *args, **kwargs):
945 > def foofoo(ui, *args, **kwargs):
946 > """Writes 'Foo foo'"""
946 > """Writes 'Foo foo'"""
947 > ui.write(b"Foo foo\\n")
947 > ui.write(b"Foo foo\\n")
948 > EOF
948 > EOF
949 $ dodopath=$TESTTMP/d/dodo.py
949 $ dodopath=$TESTTMP/d/dodo.py
950
950
951 $ echo "dodo = $dodopath" >> $HGRCPATH
951 $ echo "dodo = $dodopath" >> $HGRCPATH
952
952
953 Make sure that user is asked to enter '-v -e' to get list of built-in aliases
953 Make sure that user is asked to enter '-v -e' to get list of built-in aliases
954 $ hg help -e dodo
954 $ hg help -e dodo
955 dodo extension -
955 dodo extension -
956
956
957 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
957 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
958
958
959 list of commands:
959 list of commands:
960
960
961 dodo Does nothing
961 dodo Does nothing
962 foofoo Writes 'Foo foo'
962 foofoo Writes 'Foo foo'
963
963
964 (use 'hg help -v -e dodo' to show built-in aliases and global options)
964 (use 'hg help -v -e dodo' to show built-in aliases and global options)
965
965
966 Make sure that '-v -e' prints list of built-in aliases along with
966 Make sure that '-v -e' prints list of built-in aliases along with
967 extension help itself
967 extension help itself
968 $ hg help -v -e dodo
968 $ hg help -v -e dodo
969 dodo extension -
969 dodo extension -
970
970
971 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
971 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
972
972
973 list of commands:
973 list of commands:
974
974
975 dodo Does nothing
975 dodo Does nothing
976 foofoo Writes 'Foo foo'
976 foofoo Writes 'Foo foo'
977
977
978 global options ([+] can be repeated):
978 global options ([+] can be repeated):
979
979
980 -R --repository REPO repository root directory or name of overlay bundle
980 -R --repository REPO repository root directory or name of overlay bundle
981 file
981 file
982 --cwd DIR change working directory
982 --cwd DIR change working directory
983 -y --noninteractive do not prompt, automatically pick the first choice for
983 -y --noninteractive do not prompt, automatically pick the first choice for
984 all prompts
984 all prompts
985 -q --quiet suppress output
985 -q --quiet suppress output
986 -v --verbose enable additional output
986 -v --verbose enable additional output
987 --color TYPE when to colorize (boolean, always, auto, never, or
987 --color TYPE when to colorize (boolean, always, auto, never, or
988 debug)
988 debug)
989 --config CONFIG [+] set/override config option (use 'section.name=value')
989 --config CONFIG [+] set/override config option (use 'section.name=value')
990 --debug enable debugging output
990 --debug enable debugging output
991 --debugger start debugger
991 --debugger start debugger
992 --encoding ENCODE set the charset encoding (default: ascii)
992 --encoding ENCODE set the charset encoding (default: ascii)
993 --encodingmode MODE set the charset encoding mode (default: strict)
993 --encodingmode MODE set the charset encoding mode (default: strict)
994 --traceback always print a traceback on exception
994 --traceback always print a traceback on exception
995 --time time how long the command takes
995 --time time how long the command takes
996 --profile print command execution profile
996 --profile print command execution profile
997 --version output version information and exit
997 --version output version information and exit
998 -h --help display help and exit
998 -h --help display help and exit
999 --hidden consider hidden changesets
999 --hidden consider hidden changesets
1000 --pager TYPE when to paginate (boolean, always, auto, or never)
1000 --pager TYPE when to paginate (boolean, always, auto, or never)
1001 (default: auto)
1001 (default: auto)
1002
1002
1003 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
1003 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
1004 $ hg help -v dodo
1004 $ hg help -v dodo
1005 hg dodo
1005 hg dodo
1006
1006
1007 Does nothing
1007 Does nothing
1008
1008
1009 (use 'hg help -e dodo' to show help for the dodo extension)
1009 (use 'hg help -e dodo' to show help for the dodo extension)
1010
1010
1011 options:
1011 options:
1012
1012
1013 --mq operate on patch repository
1013 --mq operate on patch repository
1014
1014
1015 global options ([+] can be repeated):
1015 global options ([+] can be repeated):
1016
1016
1017 -R --repository REPO repository root directory or name of overlay bundle
1017 -R --repository REPO repository root directory or name of overlay bundle
1018 file
1018 file
1019 --cwd DIR change working directory
1019 --cwd DIR change working directory
1020 -y --noninteractive do not prompt, automatically pick the first choice for
1020 -y --noninteractive do not prompt, automatically pick the first choice for
1021 all prompts
1021 all prompts
1022 -q --quiet suppress output
1022 -q --quiet suppress output
1023 -v --verbose enable additional output
1023 -v --verbose enable additional output
1024 --color TYPE when to colorize (boolean, always, auto, never, or
1024 --color TYPE when to colorize (boolean, always, auto, never, or
1025 debug)
1025 debug)
1026 --config CONFIG [+] set/override config option (use 'section.name=value')
1026 --config CONFIG [+] set/override config option (use 'section.name=value')
1027 --debug enable debugging output
1027 --debug enable debugging output
1028 --debugger start debugger
1028 --debugger start debugger
1029 --encoding ENCODE set the charset encoding (default: ascii)
1029 --encoding ENCODE set the charset encoding (default: ascii)
1030 --encodingmode MODE set the charset encoding mode (default: strict)
1030 --encodingmode MODE set the charset encoding mode (default: strict)
1031 --traceback always print a traceback on exception
1031 --traceback always print a traceback on exception
1032 --time time how long the command takes
1032 --time time how long the command takes
1033 --profile print command execution profile
1033 --profile print command execution profile
1034 --version output version information and exit
1034 --version output version information and exit
1035 -h --help display help and exit
1035 -h --help display help and exit
1036 --hidden consider hidden changesets
1036 --hidden consider hidden changesets
1037 --pager TYPE when to paginate (boolean, always, auto, or never)
1037 --pager TYPE when to paginate (boolean, always, auto, or never)
1038 (default: auto)
1038 (default: auto)
1039
1039
1040 In case when extension name doesn't match any of its commands,
1040 In case when extension name doesn't match any of its commands,
1041 help message should ask for '-v' to get list of built-in aliases
1041 help message should ask for '-v' to get list of built-in aliases
1042 along with extension help
1042 along with extension help
1043 $ cat > $TESTTMP/d/dudu.py <<EOF
1043 $ cat > $TESTTMP/d/dudu.py <<EOF
1044 > """
1044 > """
1045 > This is an awesome 'dudu' extension. It does something and
1045 > This is an awesome 'dudu' extension. It does something and
1046 > also writes 'Beep beep'
1046 > also writes 'Beep beep'
1047 > """
1047 > """
1048 > from mercurial import commands, registrar
1048 > from mercurial import commands, registrar
1049 > cmdtable = {}
1049 > cmdtable = {}
1050 > command = registrar.command(cmdtable)
1050 > command = registrar.command(cmdtable)
1051 > @command(b'something', [], b'hg something')
1051 > @command(b'something', [], b'hg something')
1052 > def something(ui, *args, **kwargs):
1052 > def something(ui, *args, **kwargs):
1053 > """Does something"""
1053 > """Does something"""
1054 > ui.write(b"I do something. Yaaay\\n")
1054 > ui.write(b"I do something. Yaaay\\n")
1055 > @command(b'beep', [], b'hg beep')
1055 > @command(b'beep', [], b'hg beep')
1056 > def beep(ui, *args, **kwargs):
1056 > def beep(ui, *args, **kwargs):
1057 > """Writes 'Beep beep'"""
1057 > """Writes 'Beep beep'"""
1058 > ui.write(b"Beep beep\\n")
1058 > ui.write(b"Beep beep\\n")
1059 > EOF
1059 > EOF
1060 $ dudupath=$TESTTMP/d/dudu.py
1060 $ dudupath=$TESTTMP/d/dudu.py
1061
1061
1062 $ echo "dudu = $dudupath" >> $HGRCPATH
1062 $ echo "dudu = $dudupath" >> $HGRCPATH
1063
1063
1064 $ hg help -e dudu
1064 $ hg help -e dudu
1065 dudu extension -
1065 dudu extension -
1066
1066
1067 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1067 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1068 beep'
1068 beep'
1069
1069
1070 list of commands:
1070 list of commands:
1071
1071
1072 beep Writes 'Beep beep'
1072 beep Writes 'Beep beep'
1073 something Does something
1073 something Does something
1074
1074
1075 (use 'hg help -v dudu' to show built-in aliases and global options)
1075 (use 'hg help -v dudu' to show built-in aliases and global options)
1076
1076
1077 In case when extension name doesn't match any of its commands,
1077 In case when extension name doesn't match any of its commands,
1078 help options '-v' and '-v -e' should be equivalent
1078 help options '-v' and '-v -e' should be equivalent
1079 $ hg help -v dudu
1079 $ hg help -v dudu
1080 dudu extension -
1080 dudu extension -
1081
1081
1082 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1082 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1083 beep'
1083 beep'
1084
1084
1085 list of commands:
1085 list of commands:
1086
1086
1087 beep Writes 'Beep beep'
1087 beep Writes 'Beep beep'
1088 something Does something
1088 something Does something
1089
1089
1090 global options ([+] can be repeated):
1090 global options ([+] can be repeated):
1091
1091
1092 -R --repository REPO repository root directory or name of overlay bundle
1092 -R --repository REPO repository root directory or name of overlay bundle
1093 file
1093 file
1094 --cwd DIR change working directory
1094 --cwd DIR change working directory
1095 -y --noninteractive do not prompt, automatically pick the first choice for
1095 -y --noninteractive do not prompt, automatically pick the first choice for
1096 all prompts
1096 all prompts
1097 -q --quiet suppress output
1097 -q --quiet suppress output
1098 -v --verbose enable additional output
1098 -v --verbose enable additional output
1099 --color TYPE when to colorize (boolean, always, auto, never, or
1099 --color TYPE when to colorize (boolean, always, auto, never, or
1100 debug)
1100 debug)
1101 --config CONFIG [+] set/override config option (use 'section.name=value')
1101 --config CONFIG [+] set/override config option (use 'section.name=value')
1102 --debug enable debugging output
1102 --debug enable debugging output
1103 --debugger start debugger
1103 --debugger start debugger
1104 --encoding ENCODE set the charset encoding (default: ascii)
1104 --encoding ENCODE set the charset encoding (default: ascii)
1105 --encodingmode MODE set the charset encoding mode (default: strict)
1105 --encodingmode MODE set the charset encoding mode (default: strict)
1106 --traceback always print a traceback on exception
1106 --traceback always print a traceback on exception
1107 --time time how long the command takes
1107 --time time how long the command takes
1108 --profile print command execution profile
1108 --profile print command execution profile
1109 --version output version information and exit
1109 --version output version information and exit
1110 -h --help display help and exit
1110 -h --help display help and exit
1111 --hidden consider hidden changesets
1111 --hidden consider hidden changesets
1112 --pager TYPE when to paginate (boolean, always, auto, or never)
1112 --pager TYPE when to paginate (boolean, always, auto, or never)
1113 (default: auto)
1113 (default: auto)
1114
1114
1115 $ hg help -v -e dudu
1115 $ hg help -v -e dudu
1116 dudu extension -
1116 dudu extension -
1117
1117
1118 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1118 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1119 beep'
1119 beep'
1120
1120
1121 list of commands:
1121 list of commands:
1122
1122
1123 beep Writes 'Beep beep'
1123 beep Writes 'Beep beep'
1124 something Does something
1124 something Does something
1125
1125
1126 global options ([+] can be repeated):
1126 global options ([+] can be repeated):
1127
1127
1128 -R --repository REPO repository root directory or name of overlay bundle
1128 -R --repository REPO repository root directory or name of overlay bundle
1129 file
1129 file
1130 --cwd DIR change working directory
1130 --cwd DIR change working directory
1131 -y --noninteractive do not prompt, automatically pick the first choice for
1131 -y --noninteractive do not prompt, automatically pick the first choice for
1132 all prompts
1132 all prompts
1133 -q --quiet suppress output
1133 -q --quiet suppress output
1134 -v --verbose enable additional output
1134 -v --verbose enable additional output
1135 --color TYPE when to colorize (boolean, always, auto, never, or
1135 --color TYPE when to colorize (boolean, always, auto, never, or
1136 debug)
1136 debug)
1137 --config CONFIG [+] set/override config option (use 'section.name=value')
1137 --config CONFIG [+] set/override config option (use 'section.name=value')
1138 --debug enable debugging output
1138 --debug enable debugging output
1139 --debugger start debugger
1139 --debugger start debugger
1140 --encoding ENCODE set the charset encoding (default: ascii)
1140 --encoding ENCODE set the charset encoding (default: ascii)
1141 --encodingmode MODE set the charset encoding mode (default: strict)
1141 --encodingmode MODE set the charset encoding mode (default: strict)
1142 --traceback always print a traceback on exception
1142 --traceback always print a traceback on exception
1143 --time time how long the command takes
1143 --time time how long the command takes
1144 --profile print command execution profile
1144 --profile print command execution profile
1145 --version output version information and exit
1145 --version output version information and exit
1146 -h --help display help and exit
1146 -h --help display help and exit
1147 --hidden consider hidden changesets
1147 --hidden consider hidden changesets
1148 --pager TYPE when to paginate (boolean, always, auto, or never)
1148 --pager TYPE when to paginate (boolean, always, auto, or never)
1149 (default: auto)
1149 (default: auto)
1150
1150
1151 Disabled extension commands:
1151 Disabled extension commands:
1152
1152
1153 $ ORGHGRCPATH=$HGRCPATH
1153 $ ORGHGRCPATH=$HGRCPATH
1154 $ HGRCPATH=
1154 $ HGRCPATH=
1155 $ export HGRCPATH
1155 $ export HGRCPATH
1156 $ hg help email
1156 $ hg help email
1157 'email' is provided by the following extension:
1157 'email' is provided by the following extension:
1158
1158
1159 patchbomb command to send changesets as (a series of) patch emails
1159 patchbomb command to send changesets as (a series of) patch emails
1160
1160
1161 (use 'hg help extensions' for information on enabling extensions)
1161 (use 'hg help extensions' for information on enabling extensions)
1162
1162
1163
1163
1164 $ hg qdel
1164 $ hg qdel
1165 hg: unknown command 'qdel'
1165 hg: unknown command 'qdel'
1166 'qdelete' is provided by the following extension:
1166 'qdelete' is provided by the following extension:
1167
1167
1168 mq manage a stack of patches
1168 mq manage a stack of patches
1169
1169
1170 (use 'hg help extensions' for information on enabling extensions)
1170 (use 'hg help extensions' for information on enabling extensions)
1171 [255]
1171 [255]
1172
1172
1173
1173
1174 $ hg churn
1174 $ hg churn
1175 hg: unknown command 'churn'
1175 hg: unknown command 'churn'
1176 'churn' is provided by the following extension:
1176 'churn' is provided by the following extension:
1177
1177
1178 churn command to display statistics about repository history
1178 churn command to display statistics about repository history
1179
1179
1180 (use 'hg help extensions' for information on enabling extensions)
1180 (use 'hg help extensions' for information on enabling extensions)
1181 [255]
1181 [255]
1182
1182
1183
1183
1184
1184
1185 Disabled extensions:
1185 Disabled extensions:
1186
1186
1187 $ hg help churn
1187 $ hg help churn
1188 churn extension - command to display statistics about repository history
1188 churn extension - command to display statistics about repository history
1189
1189
1190 (use 'hg help extensions' for information on enabling extensions)
1190 (use 'hg help extensions' for information on enabling extensions)
1191
1191
1192 $ hg help patchbomb
1192 $ hg help patchbomb
1193 patchbomb extension - command to send changesets as (a series of) patch emails
1193 patchbomb extension - command to send changesets as (a series of) patch emails
1194
1194
1195 The series is started off with a "[PATCH 0 of N]" introduction, which
1195 The series is started off with a "[PATCH 0 of N]" introduction, which
1196 describes the series as a whole.
1196 describes the series as a whole.
1197
1197
1198 Each patch email has a Subject line of "[PATCH M of N] ...", using the first
1198 Each patch email has a Subject line of "[PATCH M of N] ...", using the first
1199 line of the changeset description as the subject text. The message contains
1199 line of the changeset description as the subject text. The message contains
1200 two or three body parts:
1200 two or three body parts:
1201
1201
1202 - The changeset description.
1202 - The changeset description.
1203 - [Optional] The result of running diffstat on the patch.
1203 - [Optional] The result of running diffstat on the patch.
1204 - The patch itself, as generated by 'hg export'.
1204 - The patch itself, as generated by 'hg export'.
1205
1205
1206 Each message refers to the first in the series using the In-Reply-To and
1206 Each message refers to the first in the series using the In-Reply-To and
1207 References headers, so they will show up as a sequence in threaded mail and
1207 References headers, so they will show up as a sequence in threaded mail and
1208 news readers, and in mail archives.
1208 news readers, and in mail archives.
1209
1209
1210 To configure other defaults, add a section like this to your configuration
1210 To configure other defaults, add a section like this to your configuration
1211 file:
1211 file:
1212
1212
1213 [email]
1213 [email]
1214 from = My Name <my@email>
1214 from = My Name <my@email>
1215 to = recipient1, recipient2, ...
1215 to = recipient1, recipient2, ...
1216 cc = cc1, cc2, ...
1216 cc = cc1, cc2, ...
1217 bcc = bcc1, bcc2, ...
1217 bcc = bcc1, bcc2, ...
1218 reply-to = address1, address2, ...
1218 reply-to = address1, address2, ...
1219
1219
1220 Use "[patchbomb]" as configuration section name if you need to override global
1220 Use "[patchbomb]" as configuration section name if you need to override global
1221 "[email]" address settings.
1221 "[email]" address settings.
1222
1222
1223 Then you can use the 'hg email' command to mail a series of changesets as a
1223 Then you can use the 'hg email' command to mail a series of changesets as a
1224 patchbomb.
1224 patchbomb.
1225
1225
1226 You can also either configure the method option in the email section to be a
1226 You can also either configure the method option in the email section to be a
1227 sendmail compatible mailer or fill out the [smtp] section so that the
1227 sendmail compatible mailer or fill out the [smtp] section so that the
1228 patchbomb extension can automatically send patchbombs directly from the
1228 patchbomb extension can automatically send patchbombs directly from the
1229 commandline. See the [email] and [smtp] sections in hgrc(5) for details.
1229 commandline. See the [email] and [smtp] sections in hgrc(5) for details.
1230
1230
1231 By default, 'hg email' will prompt for a "To" or "CC" header if you do not
1231 By default, 'hg email' will prompt for a "To" or "CC" header if you do not
1232 supply one via configuration or the command line. You can override this to
1232 supply one via configuration or the command line. You can override this to
1233 never prompt by configuring an empty value:
1233 never prompt by configuring an empty value:
1234
1234
1235 [email]
1235 [email]
1236 cc =
1236 cc =
1237
1237
1238 You can control the default inclusion of an introduction message with the
1238 You can control the default inclusion of an introduction message with the
1239 "patchbomb.intro" configuration option. The configuration is always
1239 "patchbomb.intro" configuration option. The configuration is always
1240 overwritten by command line flags like --intro and --desc:
1240 overwritten by command line flags like --intro and --desc:
1241
1241
1242 [patchbomb]
1242 [patchbomb]
1243 intro=auto # include introduction message if more than 1 patch (default)
1243 intro=auto # include introduction message if more than 1 patch (default)
1244 intro=never # never include an introduction message
1244 intro=never # never include an introduction message
1245 intro=always # always include an introduction message
1245 intro=always # always include an introduction message
1246
1246
1247 You can specify a template for flags to be added in subject prefixes. Flags
1247 You can specify a template for flags to be added in subject prefixes. Flags
1248 specified by --flag option are exported as "{flags}" keyword:
1248 specified by --flag option are exported as "{flags}" keyword:
1249
1249
1250 [patchbomb]
1250 [patchbomb]
1251 flagtemplate = "{separate(' ',
1251 flagtemplate = "{separate(' ',
1252 ifeq(branch, 'default', '', branch|upper),
1252 ifeq(branch, 'default', '', branch|upper),
1253 flags)}"
1253 flags)}"
1254
1254
1255 You can set patchbomb to always ask for confirmation by setting
1255 You can set patchbomb to always ask for confirmation by setting
1256 "patchbomb.confirm" to true.
1256 "patchbomb.confirm" to true.
1257
1257
1258 (use 'hg help extensions' for information on enabling extensions)
1258 (use 'hg help extensions' for information on enabling extensions)
1259
1259
1260
1260
1261 Broken disabled extension and command:
1261 Broken disabled extension and command:
1262
1262
1263 $ mkdir hgext
1263 $ mkdir hgext
1264 $ echo > hgext/__init__.py
1264 $ echo > hgext/__init__.py
1265 $ cat > hgext/broken.py <<NO_CHECK_EOF
1265 $ cat > hgext/broken.py <<NO_CHECK_EOF
1266 > "broken extension'
1266 > "broken extension'
1267 > NO_CHECK_EOF
1267 > NO_CHECK_EOF
1268 $ cat > path.py <<EOF
1268 $ cat > path.py <<EOF
1269 > import os, sys
1269 > import os, sys
1270 > sys.path.insert(0, os.environ['HGEXTPATH'])
1270 > sys.path.insert(0, os.environ['HGEXTPATH'])
1271 > EOF
1271 > EOF
1272 $ HGEXTPATH=`pwd`
1272 $ HGEXTPATH=`pwd`
1273 $ export HGEXTPATH
1273 $ export HGEXTPATH
1274
1274
1275 $ hg --config extensions.path=./path.py help broken
1275 $ hg --config extensions.path=./path.py help broken
1276 broken extension - (no help text available)
1276 broken extension - (no help text available)
1277
1277
1278 (use 'hg help extensions' for information on enabling extensions)
1278 (use 'hg help extensions' for information on enabling extensions)
1279
1279
1280
1280
1281 $ cat > hgext/forest.py <<EOF
1281 $ cat > hgext/forest.py <<EOF
1282 > cmdtable = None
1282 > cmdtable = None
1283 > @command()
1283 > @command()
1284 > def f():
1284 > def f():
1285 > pass
1285 > pass
1286 > @command(123)
1286 > @command(123)
1287 > def g():
1287 > def g():
1288 > pass
1288 > pass
1289 > EOF
1289 > EOF
1290 $ hg --config extensions.path=./path.py help foo
1290 $ hg --config extensions.path=./path.py help foo
1291 abort: no such help topic: foo
1291 abort: no such help topic: foo
1292 (try 'hg help --keyword foo')
1292 (try 'hg help --keyword foo')
1293 [255]
1293 [255]
1294
1294
1295 $ cat > throw.py <<EOF
1295 $ cat > throw.py <<EOF
1296 > from mercurial import commands, registrar, util
1296 > from mercurial import commands, registrar, util
1297 > cmdtable = {}
1297 > cmdtable = {}
1298 > command = registrar.command(cmdtable)
1298 > command = registrar.command(cmdtable)
1299 > class Bogon(Exception): pass
1299 > class Bogon(Exception): pass
1300 > @command(b'throw', [], b'hg throw', norepo=True)
1300 > @command(b'throw', [], b'hg throw', norepo=True)
1301 > def throw(ui, **opts):
1301 > def throw(ui, **opts):
1302 > """throws an exception"""
1302 > """throws an exception"""
1303 > raise Bogon()
1303 > raise Bogon()
1304 > EOF
1304 > EOF
1305
1305
1306 No declared supported version, extension complains:
1306 No declared supported version, extension complains:
1307 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1307 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1308 ** Unknown exception encountered with possibly-broken third-party extension throw
1308 ** Unknown exception encountered with possibly-broken third-party extension throw
1309 ** which supports versions unknown of Mercurial.
1309 ** which supports versions unknown of Mercurial.
1310 ** Please disable throw and try your action again.
1310 ** Please disable throw and try your action again.
1311 ** If that fixes the bug please report it to the extension author.
1311 ** If that fixes the bug please report it to the extension author.
1312 ** Python * (glob)
1312 ** Python * (glob)
1313 ** Mercurial Distributed SCM * (glob)
1313 ** Mercurial Distributed SCM * (glob)
1314 ** Extensions loaded: throw
1314 ** Extensions loaded: throw
1315
1315
1316 empty declaration of supported version, extension complains:
1316 empty declaration of supported version, extension complains:
1317 $ echo "testedwith = ''" >> throw.py
1317 $ echo "testedwith = ''" >> throw.py
1318 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1318 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1319 ** Unknown exception encountered with possibly-broken third-party extension throw
1319 ** Unknown exception encountered with possibly-broken third-party extension throw
1320 ** which supports versions unknown of Mercurial.
1320 ** which supports versions unknown of Mercurial.
1321 ** Please disable throw and try your action again.
1321 ** Please disable throw and try your action again.
1322 ** If that fixes the bug please report it to the extension author.
1322 ** If that fixes the bug please report it to the extension author.
1323 ** Python * (glob)
1323 ** Python * (glob)
1324 ** Mercurial Distributed SCM (*) (glob)
1324 ** Mercurial Distributed SCM (*) (glob)
1325 ** Extensions loaded: throw
1325 ** Extensions loaded: throw
1326
1326
1327 If the extension specifies a buglink, show that:
1327 If the extension specifies a buglink, show that:
1328 $ echo 'buglink = "http://example.com/bts"' >> throw.py
1328 $ echo 'buglink = "http://example.com/bts"' >> throw.py
1329 $ rm -f throw.pyc throw.pyo
1329 $ rm -f throw.pyc throw.pyo
1330 $ rm -Rf __pycache__
1330 $ rm -Rf __pycache__
1331 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1331 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1332 ** Unknown exception encountered with possibly-broken third-party extension throw
1332 ** Unknown exception encountered with possibly-broken third-party extension throw
1333 ** which supports versions unknown of Mercurial.
1333 ** which supports versions unknown of Mercurial.
1334 ** Please disable throw and try your action again.
1334 ** Please disable throw and try your action again.
1335 ** If that fixes the bug please report it to http://example.com/bts
1335 ** If that fixes the bug please report it to http://example.com/bts
1336 ** Python * (glob)
1336 ** Python * (glob)
1337 ** Mercurial Distributed SCM (*) (glob)
1337 ** Mercurial Distributed SCM (*) (glob)
1338 ** Extensions loaded: throw
1338 ** Extensions loaded: throw
1339
1339
1340 If the extensions declare outdated versions, accuse the older extension first:
1340 If the extensions declare outdated versions, accuse the older extension first:
1341 $ echo "from mercurial import util" >> older.py
1341 $ echo "from mercurial import util" >> older.py
1342 $ echo "util.version = lambda:b'2.2'" >> older.py
1342 $ echo "util.version = lambda:b'2.2'" >> older.py
1343 $ echo "testedwith = b'1.9.3'" >> older.py
1343 $ echo "testedwith = b'1.9.3'" >> older.py
1344 $ echo "testedwith = b'2.1.1'" >> throw.py
1344 $ echo "testedwith = b'2.1.1'" >> throw.py
1345 $ rm -f throw.pyc throw.pyo
1345 $ rm -f throw.pyc throw.pyo
1346 $ rm -Rf __pycache__
1346 $ rm -Rf __pycache__
1347 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1347 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1348 > throw 2>&1 | egrep '^\*\*'
1348 > throw 2>&1 | egrep '^\*\*'
1349 ** Unknown exception encountered with possibly-broken third-party extension older
1349 ** Unknown exception encountered with possibly-broken third-party extension older
1350 ** which supports versions 1.9 of Mercurial.
1350 ** which supports versions 1.9 of Mercurial.
1351 ** Please disable older and try your action again.
1351 ** Please disable older and try your action again.
1352 ** If that fixes the bug please report it to the extension author.
1352 ** If that fixes the bug please report it to the extension author.
1353 ** Python * (glob)
1353 ** Python * (glob)
1354 ** Mercurial Distributed SCM (version 2.2)
1354 ** Mercurial Distributed SCM (version 2.2)
1355 ** Extensions loaded: throw, older
1355 ** Extensions loaded: throw, older
1356
1356
1357 One extension only tested with older, one only with newer versions:
1357 One extension only tested with older, one only with newer versions:
1358 $ echo "util.version = lambda:b'2.1'" >> older.py
1358 $ echo "util.version = lambda:b'2.1'" >> older.py
1359 $ rm -f older.pyc older.pyo
1359 $ rm -f older.pyc older.pyo
1360 $ rm -Rf __pycache__
1360 $ rm -Rf __pycache__
1361 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1361 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1362 > throw 2>&1 | egrep '^\*\*'
1362 > throw 2>&1 | egrep '^\*\*'
1363 ** Unknown exception encountered with possibly-broken third-party extension older
1363 ** Unknown exception encountered with possibly-broken third-party extension older
1364 ** which supports versions 1.9 of Mercurial.
1364 ** which supports versions 1.9 of Mercurial.
1365 ** Please disable older and try your action again.
1365 ** Please disable older and try your action again.
1366 ** If that fixes the bug please report it to the extension author.
1366 ** If that fixes the bug please report it to the extension author.
1367 ** Python * (glob)
1367 ** Python * (glob)
1368 ** Mercurial Distributed SCM (version 2.1)
1368 ** Mercurial Distributed SCM (version 2.1)
1369 ** Extensions loaded: throw, older
1369 ** Extensions loaded: throw, older
1370
1370
1371 Older extension is tested with current version, the other only with newer:
1371 Older extension is tested with current version, the other only with newer:
1372 $ echo "util.version = lambda:b'1.9.3'" >> older.py
1372 $ echo "util.version = lambda:b'1.9.3'" >> older.py
1373 $ rm -f older.pyc older.pyo
1373 $ rm -f older.pyc older.pyo
1374 $ rm -Rf __pycache__
1374 $ rm -Rf __pycache__
1375 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1375 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1376 > throw 2>&1 | egrep '^\*\*'
1376 > throw 2>&1 | egrep '^\*\*'
1377 ** Unknown exception encountered with possibly-broken third-party extension throw
1377 ** Unknown exception encountered with possibly-broken third-party extension throw
1378 ** which supports versions 2.1 of Mercurial.
1378 ** which supports versions 2.1 of Mercurial.
1379 ** Please disable throw and try your action again.
1379 ** Please disable throw and try your action again.
1380 ** If that fixes the bug please report it to http://example.com/bts
1380 ** If that fixes the bug please report it to http://example.com/bts
1381 ** Python * (glob)
1381 ** Python * (glob)
1382 ** Mercurial Distributed SCM (version 1.9.3)
1382 ** Mercurial Distributed SCM (version 1.9.3)
1383 ** Extensions loaded: throw, older
1383 ** Extensions loaded: throw, older
1384
1384
1385 Ability to point to a different point
1385 Ability to point to a different point
1386 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1386 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1387 > --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
1387 > --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
1388 ** unknown exception encountered, please report by visiting
1388 ** unknown exception encountered, please report by visiting
1389 ** Your Local Goat Lenders
1389 ** Your Local Goat Lenders
1390 ** Python * (glob)
1390 ** Python * (glob)
1391 ** Mercurial Distributed SCM (*) (glob)
1391 ** Mercurial Distributed SCM (*) (glob)
1392 ** Extensions loaded: throw, older
1392 ** Extensions loaded: throw, older
1393
1393
1394 Declare the version as supporting this hg version, show regular bts link:
1394 Declare the version as supporting this hg version, show regular bts link:
1395 $ hgver=`hg debuginstall -T '{hgver}'`
1395 $ hgver=`hg debuginstall -T '{hgver}'`
1396 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
1396 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
1397 $ if [ -z "$hgver" ]; then
1397 $ if [ -z "$hgver" ]; then
1398 > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
1398 > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
1399 > fi
1399 > fi
1400 $ rm -f throw.pyc throw.pyo
1400 $ rm -f throw.pyc throw.pyo
1401 $ rm -Rf __pycache__
1401 $ rm -Rf __pycache__
1402 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1402 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1403 ** unknown exception encountered, please report by visiting
1403 ** unknown exception encountered, please report by visiting
1404 ** https://mercurial-scm.org/wiki/BugTracker
1404 ** https://mercurial-scm.org/wiki/BugTracker
1405 ** Python * (glob)
1405 ** Python * (glob)
1406 ** Mercurial Distributed SCM (*) (glob)
1406 ** Mercurial Distributed SCM (*) (glob)
1407 ** Extensions loaded: throw
1407 ** Extensions loaded: throw
1408
1408
1409 Patch version is ignored during compatibility check
1409 Patch version is ignored during compatibility check
1410 $ echo "testedwith = b'3.2'" >> throw.py
1410 $ echo "testedwith = b'3.2'" >> throw.py
1411 $ echo "util.version = lambda:b'3.2.2'" >> throw.py
1411 $ echo "util.version = lambda:b'3.2.2'" >> throw.py
1412 $ rm -f throw.pyc throw.pyo
1412 $ rm -f throw.pyc throw.pyo
1413 $ rm -Rf __pycache__
1413 $ rm -Rf __pycache__
1414 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1414 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1415 ** unknown exception encountered, please report by visiting
1415 ** unknown exception encountered, please report by visiting
1416 ** https://mercurial-scm.org/wiki/BugTracker
1416 ** https://mercurial-scm.org/wiki/BugTracker
1417 ** Python * (glob)
1417 ** Python * (glob)
1418 ** Mercurial Distributed SCM (*) (glob)
1418 ** Mercurial Distributed SCM (*) (glob)
1419 ** Extensions loaded: throw
1419 ** Extensions loaded: throw
1420
1420
1421 Test version number support in 'hg version':
1421 Test version number support in 'hg version':
1422 $ echo '__version__ = (1, 2, 3)' >> throw.py
1422 $ echo '__version__ = (1, 2, 3)' >> throw.py
1423 $ rm -f throw.pyc throw.pyo
1423 $ rm -f throw.pyc throw.pyo
1424 $ rm -Rf __pycache__
1424 $ rm -Rf __pycache__
1425 $ hg version -v
1425 $ hg version -v
1426 Mercurial Distributed SCM (version *) (glob)
1426 Mercurial Distributed SCM (version *) (glob)
1427 (see https://mercurial-scm.org for more information)
1427 (see https://mercurial-scm.org for more information)
1428
1428
1429 Copyright (C) 2005-* Matt Mackall and others (glob)
1429 Copyright (C) 2005-* Matt Mackall and others (glob)
1430 This is free software; see the source for copying conditions. There is NO
1430 This is free software; see the source for copying conditions. There is NO
1431 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1431 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1432
1432
1433 Enabled extensions:
1433 Enabled extensions:
1434
1434
1435
1435
1436 $ hg version -v --config extensions.throw=throw.py
1436 $ hg version -v --config extensions.throw=throw.py
1437 Mercurial Distributed SCM (version *) (glob)
1437 Mercurial Distributed SCM (version *) (glob)
1438 (see https://mercurial-scm.org for more information)
1438 (see https://mercurial-scm.org for more information)
1439
1439
1440 Copyright (C) 2005-* Matt Mackall and others (glob)
1440 Copyright (C) 2005-* Matt Mackall and others (glob)
1441 This is free software; see the source for copying conditions. There is NO
1441 This is free software; see the source for copying conditions. There is NO
1442 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1442 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1443
1443
1444 Enabled extensions:
1444 Enabled extensions:
1445
1445
1446 throw external 1.2.3
1446 throw external 1.2.3
1447 $ echo 'getversion = lambda: b"1.twentythree"' >> throw.py
1447 $ echo 'getversion = lambda: b"1.twentythree"' >> throw.py
1448 $ rm -f throw.pyc throw.pyo
1448 $ rm -f throw.pyc throw.pyo
1449 $ rm -Rf __pycache__
1449 $ rm -Rf __pycache__
1450 $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
1450 $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
1451 Mercurial Distributed SCM (version *) (glob)
1451 Mercurial Distributed SCM (version *) (glob)
1452 (see https://mercurial-scm.org for more information)
1452 (see https://mercurial-scm.org for more information)
1453
1453
1454 Copyright (C) 2005-* Matt Mackall and others (glob)
1454 Copyright (C) 2005-* Matt Mackall and others (glob)
1455 This is free software; see the source for copying conditions. There is NO
1455 This is free software; see the source for copying conditions. There is NO
1456 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1456 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1457
1457
1458 Enabled extensions:
1458 Enabled extensions:
1459
1459
1460 throw external 1.twentythree
1460 throw external 1.twentythree
1461 strip internal
1461 strip internal
1462
1462
1463 $ hg version -q --config extensions.throw=throw.py
1463 $ hg version -q --config extensions.throw=throw.py
1464 Mercurial Distributed SCM (version *) (glob)
1464 Mercurial Distributed SCM (version *) (glob)
1465
1465
1466 Test template output:
1466 Test template output:
1467
1467
1468 $ hg version --config extensions.strip= -T'{extensions}'
1468 $ hg version --config extensions.strip= -T'{extensions}'
1469 strip
1469 strip
1470
1470
1471 Test JSON output of version:
1471 Test JSON output of version:
1472
1472
1473 $ hg version -Tjson
1473 $ hg version -Tjson
1474 [
1474 [
1475 {
1475 {
1476 "extensions": [],
1476 "extensions": [],
1477 "ver": "*" (glob)
1477 "ver": "*" (glob)
1478 }
1478 }
1479 ]
1479 ]
1480
1480
1481 $ hg version --config extensions.throw=throw.py -Tjson
1481 $ hg version --config extensions.throw=throw.py -Tjson
1482 [
1482 [
1483 {
1483 {
1484 "extensions": [{"bundled": false, "name": "throw", "ver": "1.twentythree"}],
1484 "extensions": [{"bundled": false, "name": "throw", "ver": "1.twentythree"}],
1485 "ver": "3.2.2"
1485 "ver": "3.2.2"
1486 }
1486 }
1487 ]
1487 ]
1488
1488
1489 $ hg version --config extensions.strip= -Tjson
1489 $ hg version --config extensions.strip= -Tjson
1490 [
1490 [
1491 {
1491 {
1492 "extensions": [{"bundled": true, "name": "strip", "ver": null}],
1492 "extensions": [{"bundled": true, "name": "strip", "ver": null}],
1493 "ver": "*" (glob)
1493 "ver": "*" (glob)
1494 }
1494 }
1495 ]
1495 ]
1496
1496
1497 Test template output of version:
1497 Test template output of version:
1498
1498
1499 $ hg version --config extensions.throw=throw.py --config extensions.strip= \
1499 $ hg version --config extensions.throw=throw.py --config extensions.strip= \
1500 > -T'{extensions % "{name} {pad(ver, 16)} ({if(bundled, "internal", "external")})\n"}'
1500 > -T'{extensions % "{name} {pad(ver, 16)} ({if(bundled, "internal", "external")})\n"}'
1501 throw 1.twentythree (external)
1501 throw 1.twentythree (external)
1502 strip (internal)
1502 strip (internal)
1503
1503
1504 Refuse to load extensions with minimum version requirements
1504 Refuse to load extensions with minimum version requirements
1505
1505
1506 $ cat > minversion1.py << EOF
1506 $ cat > minversion1.py << EOF
1507 > from mercurial import util
1507 > from mercurial import util
1508 > util.version = lambda: b'3.5.2'
1508 > util.version = lambda: b'3.5.2'
1509 > minimumhgversion = b'3.6'
1509 > minimumhgversion = b'3.6'
1510 > EOF
1510 > EOF
1511 $ hg --config extensions.minversion=minversion1.py version
1511 $ hg --config extensions.minversion=minversion1.py version
1512 (third party extension minversion requires version 3.6 or newer of Mercurial; disabling)
1512 (third party extension minversion requires version 3.6 or newer of Mercurial; disabling)
1513 Mercurial Distributed SCM (version 3.5.2)
1513 Mercurial Distributed SCM (version 3.5.2)
1514 (see https://mercurial-scm.org for more information)
1514 (see https://mercurial-scm.org for more information)
1515
1515
1516 Copyright (C) 2005-* Matt Mackall and others (glob)
1516 Copyright (C) 2005-* Matt Mackall and others (glob)
1517 This is free software; see the source for copying conditions. There is NO
1517 This is free software; see the source for copying conditions. There is NO
1518 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1518 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1519
1519
1520 $ cat > minversion2.py << EOF
1520 $ cat > minversion2.py << EOF
1521 > from mercurial import util
1521 > from mercurial import util
1522 > util.version = lambda: b'3.6'
1522 > util.version = lambda: b'3.6'
1523 > minimumhgversion = b'3.7'
1523 > minimumhgversion = b'3.7'
1524 > EOF
1524 > EOF
1525 $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
1525 $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
1526 (third party extension minversion requires version 3.7 or newer of Mercurial; disabling)
1526 (third party extension minversion requires version 3.7 or newer of Mercurial; disabling)
1527
1527
1528 Can load version that is only off by point release
1528 Can load version that is only off by point release
1529
1529
1530 $ cat > minversion2.py << EOF
1530 $ cat > minversion2.py << EOF
1531 > from mercurial import util
1531 > from mercurial import util
1532 > util.version = lambda: b'3.6.1'
1532 > util.version = lambda: b'3.6.1'
1533 > minimumhgversion = b'3.6'
1533 > minimumhgversion = b'3.6'
1534 > EOF
1534 > EOF
1535 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1535 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1536 [1]
1536 [1]
1537
1537
1538 Can load minimum version identical to current
1538 Can load minimum version identical to current
1539
1539
1540 $ cat > minversion3.py << EOF
1540 $ cat > minversion3.py << EOF
1541 > from mercurial import util
1541 > from mercurial import util
1542 > util.version = lambda: b'3.5'
1542 > util.version = lambda: b'3.5'
1543 > minimumhgversion = b'3.5'
1543 > minimumhgversion = b'3.5'
1544 > EOF
1544 > EOF
1545 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1545 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1546 [1]
1546 [1]
1547
1547
1548 Restore HGRCPATH
1548 Restore HGRCPATH
1549
1549
1550 $ HGRCPATH=$ORGHGRCPATH
1550 $ HGRCPATH=$ORGHGRCPATH
1551 $ export HGRCPATH
1551 $ export HGRCPATH
1552
1552
1553 Commands handling multiple repositories at a time should invoke only
1553 Commands handling multiple repositories at a time should invoke only
1554 "reposetup()" of extensions enabling in the target repository.
1554 "reposetup()" of extensions enabling in the target repository.
1555
1555
1556 $ mkdir reposetup-test
1556 $ mkdir reposetup-test
1557 $ cd reposetup-test
1557 $ cd reposetup-test
1558
1558
1559 $ cat > $TESTTMP/reposetuptest.py <<EOF
1559 $ cat > $TESTTMP/reposetuptest.py <<EOF
1560 > from mercurial import extensions
1560 > from mercurial import extensions
1561 > def reposetup(ui, repo):
1561 > def reposetup(ui, repo):
1562 > ui.write(b'reposetup() for %s\n' % (repo.root))
1562 > ui.write(b'reposetup() for %s\n' % (repo.root))
1563 > ui.flush()
1563 > ui.flush()
1564 > EOF
1564 > EOF
1565 $ hg init src
1565 $ hg init src
1566 $ echo a > src/a
1566 $ echo a > src/a
1567 $ hg -R src commit -Am '#0 at src/a'
1567 $ hg -R src commit -Am '#0 at src/a'
1568 adding a
1568 adding a
1569 $ echo '[extensions]' >> src/.hg/hgrc
1569 $ echo '[extensions]' >> src/.hg/hgrc
1570 $ echo '# enable extension locally' >> src/.hg/hgrc
1570 $ echo '# enable extension locally' >> src/.hg/hgrc
1571 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
1571 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
1572 $ hg -R src status
1572 $ hg -R src status
1573 reposetup() for $TESTTMP/reposetup-test/src
1573 reposetup() for $TESTTMP/reposetup-test/src
1574 reposetup() for $TESTTMP/reposetup-test/src (chg !)
1574 reposetup() for $TESTTMP/reposetup-test/src (chg !)
1575
1575
1576 #if no-extraextensions
1576 #if no-extraextensions
1577 $ hg --cwd src debugextensions
1577 $ hg --cwd src debugextensions
1578 reposetup() for $TESTTMP/reposetup-test/src
1578 reposetup() for $TESTTMP/reposetup-test/src
1579 dodo (untested!)
1579 dodo (untested!)
1580 dudu (untested!)
1580 dudu (untested!)
1581 mq
1581 mq
1582 reposetuptest (untested!)
1582 reposetuptest (untested!)
1583 strip
1583 strip
1584 #endif
1584 #endif
1585
1585
1586 $ hg clone -U src clone-dst1
1586 $ hg clone -U src clone-dst1
1587 reposetup() for $TESTTMP/reposetup-test/src
1587 reposetup() for $TESTTMP/reposetup-test/src
1588 $ hg init push-dst1
1588 $ hg init push-dst1
1589 $ hg -q -R src push push-dst1
1589 $ hg -q -R src push push-dst1
1590 reposetup() for $TESTTMP/reposetup-test/src
1590 reposetup() for $TESTTMP/reposetup-test/src
1591 $ hg init pull-src1
1591 $ hg init pull-src1
1592 $ hg -q -R pull-src1 pull src
1592 $ hg -q -R pull-src1 pull src
1593 reposetup() for $TESTTMP/reposetup-test/src
1593 reposetup() for $TESTTMP/reposetup-test/src
1594
1594
1595 $ cat <<EOF >> $HGRCPATH
1595 $ cat <<EOF >> $HGRCPATH
1596 > [extensions]
1596 > [extensions]
1597 > # disable extension globally and explicitly
1597 > # disable extension globally and explicitly
1598 > reposetuptest = !
1598 > reposetuptest = !
1599 > EOF
1599 > EOF
1600 $ hg clone -U src clone-dst2
1600 $ hg clone -U src clone-dst2
1601 reposetup() for $TESTTMP/reposetup-test/src
1601 reposetup() for $TESTTMP/reposetup-test/src
1602 $ hg init push-dst2
1602 $ hg init push-dst2
1603 $ hg -q -R src push push-dst2
1603 $ hg -q -R src push push-dst2
1604 reposetup() for $TESTTMP/reposetup-test/src
1604 reposetup() for $TESTTMP/reposetup-test/src
1605 $ hg init pull-src2
1605 $ hg init pull-src2
1606 $ hg -q -R pull-src2 pull src
1606 $ hg -q -R pull-src2 pull src
1607 reposetup() for $TESTTMP/reposetup-test/src
1607 reposetup() for $TESTTMP/reposetup-test/src
1608
1608
1609 $ cat <<EOF >> $HGRCPATH
1609 $ cat <<EOF >> $HGRCPATH
1610 > [extensions]
1610 > [extensions]
1611 > # enable extension globally
1611 > # enable extension globally
1612 > reposetuptest = $TESTTMP/reposetuptest.py
1612 > reposetuptest = $TESTTMP/reposetuptest.py
1613 > EOF
1613 > EOF
1614 $ hg clone -U src clone-dst3
1614 $ hg clone -U src clone-dst3
1615 reposetup() for $TESTTMP/reposetup-test/src
1615 reposetup() for $TESTTMP/reposetup-test/src
1616 reposetup() for $TESTTMP/reposetup-test/clone-dst3
1616 reposetup() for $TESTTMP/reposetup-test/clone-dst3
1617 $ hg init push-dst3
1617 $ hg init push-dst3
1618 reposetup() for $TESTTMP/reposetup-test/push-dst3
1618 reposetup() for $TESTTMP/reposetup-test/push-dst3
1619 $ hg -q -R src push push-dst3
1619 $ hg -q -R src push push-dst3
1620 reposetup() for $TESTTMP/reposetup-test/src
1620 reposetup() for $TESTTMP/reposetup-test/src
1621 reposetup() for $TESTTMP/reposetup-test/push-dst3
1621 reposetup() for $TESTTMP/reposetup-test/push-dst3
1622 $ hg init pull-src3
1622 $ hg init pull-src3
1623 reposetup() for $TESTTMP/reposetup-test/pull-src3
1623 reposetup() for $TESTTMP/reposetup-test/pull-src3
1624 $ hg -q -R pull-src3 pull src
1624 $ hg -q -R pull-src3 pull src
1625 reposetup() for $TESTTMP/reposetup-test/pull-src3
1625 reposetup() for $TESTTMP/reposetup-test/pull-src3
1626 reposetup() for $TESTTMP/reposetup-test/src
1626 reposetup() for $TESTTMP/reposetup-test/src
1627
1627
1628 $ echo '[extensions]' >> src/.hg/hgrc
1628 $ echo '[extensions]' >> src/.hg/hgrc
1629 $ echo '# disable extension locally' >> src/.hg/hgrc
1629 $ echo '# disable extension locally' >> src/.hg/hgrc
1630 $ echo 'reposetuptest = !' >> src/.hg/hgrc
1630 $ echo 'reposetuptest = !' >> src/.hg/hgrc
1631 $ hg clone -U src clone-dst4
1631 $ hg clone -U src clone-dst4
1632 reposetup() for $TESTTMP/reposetup-test/clone-dst4
1632 reposetup() for $TESTTMP/reposetup-test/clone-dst4
1633 $ hg init push-dst4
1633 $ hg init push-dst4
1634 reposetup() for $TESTTMP/reposetup-test/push-dst4
1634 reposetup() for $TESTTMP/reposetup-test/push-dst4
1635 $ hg -q -R src push push-dst4
1635 $ hg -q -R src push push-dst4
1636 reposetup() for $TESTTMP/reposetup-test/push-dst4
1636 reposetup() for $TESTTMP/reposetup-test/push-dst4
1637 $ hg init pull-src4
1637 $ hg init pull-src4
1638 reposetup() for $TESTTMP/reposetup-test/pull-src4
1638 reposetup() for $TESTTMP/reposetup-test/pull-src4
1639 $ hg -q -R pull-src4 pull src
1639 $ hg -q -R pull-src4 pull src
1640 reposetup() for $TESTTMP/reposetup-test/pull-src4
1640 reposetup() for $TESTTMP/reposetup-test/pull-src4
1641
1641
1642 disabling in command line overlays with all configuration
1642 disabling in command line overlays with all configuration
1643 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
1643 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
1644 $ hg --config extensions.reposetuptest=! init push-dst5
1644 $ hg --config extensions.reposetuptest=! init push-dst5
1645 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
1645 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
1646 $ hg --config extensions.reposetuptest=! init pull-src5
1646 $ hg --config extensions.reposetuptest=! init pull-src5
1647 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
1647 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
1648
1648
1649 $ cat <<EOF >> $HGRCPATH
1649 $ cat <<EOF >> $HGRCPATH
1650 > [extensions]
1650 > [extensions]
1651 > # disable extension globally and explicitly
1651 > # disable extension globally and explicitly
1652 > reposetuptest = !
1652 > reposetuptest = !
1653 > EOF
1653 > EOF
1654 $ hg init parent
1654 $ hg init parent
1655 $ hg init parent/sub1
1655 $ hg init parent/sub1
1656 $ echo 1 > parent/sub1/1
1656 $ echo 1 > parent/sub1/1
1657 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
1657 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
1658 adding 1
1658 adding 1
1659 $ hg init parent/sub2
1659 $ hg init parent/sub2
1660 $ hg init parent/sub2/sub21
1660 $ hg init parent/sub2/sub21
1661 $ echo 21 > parent/sub2/sub21/21
1661 $ echo 21 > parent/sub2/sub21/21
1662 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
1662 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
1663 adding 21
1663 adding 21
1664 $ cat > parent/sub2/.hgsub <<EOF
1664 $ cat > parent/sub2/.hgsub <<EOF
1665 > sub21 = sub21
1665 > sub21 = sub21
1666 > EOF
1666 > EOF
1667 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
1667 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
1668 adding .hgsub
1668 adding .hgsub
1669 $ hg init parent/sub3
1669 $ hg init parent/sub3
1670 $ echo 3 > parent/sub3/3
1670 $ echo 3 > parent/sub3/3
1671 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
1671 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
1672 adding 3
1672 adding 3
1673 $ cat > parent/.hgsub <<EOF
1673 $ cat > parent/.hgsub <<EOF
1674 > sub1 = sub1
1674 > sub1 = sub1
1675 > sub2 = sub2
1675 > sub2 = sub2
1676 > sub3 = sub3
1676 > sub3 = sub3
1677 > EOF
1677 > EOF
1678 $ hg -R parent commit -Am '#0 at parent'
1678 $ hg -R parent commit -Am '#0 at parent'
1679 adding .hgsub
1679 adding .hgsub
1680 $ echo '[extensions]' >> parent/.hg/hgrc
1680 $ echo '[extensions]' >> parent/.hg/hgrc
1681 $ echo '# enable extension locally' >> parent/.hg/hgrc
1681 $ echo '# enable extension locally' >> parent/.hg/hgrc
1682 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
1682 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
1683 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
1683 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
1684 $ hg -R parent status -S -A
1684 $ hg -R parent status -S -A
1685 reposetup() for $TESTTMP/reposetup-test/parent
1685 reposetup() for $TESTTMP/reposetup-test/parent
1686 reposetup() for $TESTTMP/reposetup-test/parent/sub2
1686 reposetup() for $TESTTMP/reposetup-test/parent/sub2
1687 C .hgsub
1687 C .hgsub
1688 C .hgsubstate
1688 C .hgsubstate
1689 C sub1/1
1689 C sub1/1
1690 C sub2/.hgsub
1690 C sub2/.hgsub
1691 C sub2/.hgsubstate
1691 C sub2/.hgsubstate
1692 C sub2/sub21/21
1692 C sub2/sub21/21
1693 C sub3/3
1693 C sub3/3
1694
1694
1695 $ cd ..
1695 $ cd ..
1696
1696
1697 Prohibit registration of commands that don't use @command (issue5137)
1697 Prohibit registration of commands that don't use @command (issue5137)
1698
1698
1699 $ hg init deprecated
1699 $ hg init deprecated
1700 $ cd deprecated
1700 $ cd deprecated
1701
1701
1702 $ cat <<EOF > deprecatedcmd.py
1702 $ cat <<EOF > deprecatedcmd.py
1703 > def deprecatedcmd(repo, ui):
1703 > def deprecatedcmd(repo, ui):
1704 > pass
1704 > pass
1705 > cmdtable = {
1705 > cmdtable = {
1706 > b'deprecatedcmd': (deprecatedcmd, [], b''),
1706 > b'deprecatedcmd': (deprecatedcmd, [], b''),
1707 > }
1707 > }
1708 > EOF
1708 > EOF
1709 $ cat <<EOF > .hg/hgrc
1709 $ cat <<EOF > .hg/hgrc
1710 > [extensions]
1710 > [extensions]
1711 > deprecatedcmd = `pwd`/deprecatedcmd.py
1711 > deprecatedcmd = `pwd`/deprecatedcmd.py
1712 > mq = !
1712 > mq = !
1713 > hgext.mq = !
1713 > hgext.mq = !
1714 > hgext/mq = !
1714 > hgext/mq = !
1715 > EOF
1715 > EOF
1716
1716
1717 $ hg deprecatedcmd > /dev/null
1717 $ hg deprecatedcmd > /dev/null
1718 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1718 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1719 *** (use @command decorator to register 'deprecatedcmd')
1719 *** (use @command decorator to register 'deprecatedcmd')
1720 hg: unknown command 'deprecatedcmd'
1720 hg: unknown command 'deprecatedcmd'
1721 (use 'hg help' for a list of commands)
1721 (use 'hg help' for a list of commands)
1722 [255]
1722 [255]
1723
1723
1724 the extension shouldn't be loaded at all so the mq works:
1724 the extension shouldn't be loaded at all so the mq works:
1725
1725
1726 $ hg qseries --config extensions.mq= > /dev/null
1726 $ hg qseries --config extensions.mq= > /dev/null
1727 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1727 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1728 *** (use @command decorator to register 'deprecatedcmd')
1728 *** (use @command decorator to register 'deprecatedcmd')
1729
1729
1730 $ cd ..
1730 $ cd ..
1731
1731
1732 Test synopsis and docstring extending
1732 Test synopsis and docstring extending
1733
1733
1734 $ hg init exthelp
1734 $ hg init exthelp
1735 $ cat > exthelp.py <<EOF
1735 $ cat > exthelp.py <<EOF
1736 > from mercurial import commands, extensions
1736 > from mercurial import commands, extensions
1737 > def exbookmarks(orig, *args, **opts):
1737 > def exbookmarks(orig, *args, **opts):
1738 > return orig(*args, **opts)
1738 > return orig(*args, **opts)
1739 > def uisetup(ui):
1739 > def uisetup(ui):
1740 > synopsis = b' GREPME [--foo] [-x]'
1740 > synopsis = b' GREPME [--foo] [-x]'
1741 > docstring = '''
1741 > docstring = '''
1742 > GREPME make sure that this is in the help!
1742 > GREPME make sure that this is in the help!
1743 > '''
1743 > '''
1744 > extensions.wrapcommand(commands.table, b'bookmarks', exbookmarks,
1744 > extensions.wrapcommand(commands.table, b'bookmarks', exbookmarks,
1745 > synopsis, docstring)
1745 > synopsis, docstring)
1746 > EOF
1746 > EOF
1747 $ abspath=`pwd`/exthelp.py
1747 $ abspath=`pwd`/exthelp.py
1748 $ echo '[extensions]' >> $HGRCPATH
1748 $ echo '[extensions]' >> $HGRCPATH
1749 $ echo "exthelp = $abspath" >> $HGRCPATH
1749 $ echo "exthelp = $abspath" >> $HGRCPATH
1750 $ cd exthelp
1750 $ cd exthelp
1751 $ hg help bookmarks | grep GREPME
1751 $ hg help bookmarks | grep GREPME
1752 hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
1752 hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
1753 GREPME make sure that this is in the help!
1753 GREPME make sure that this is in the help!
1754 $ cd ..
1754 $ cd ..
1755
1755
1756 Show deprecation warning for the use of cmdutil.command
1756 Show deprecation warning for the use of cmdutil.command
1757
1757
1758 $ cat > nonregistrar.py <<EOF
1758 $ cat > nonregistrar.py <<EOF
1759 > from mercurial import cmdutil
1759 > from mercurial import cmdutil
1760 > cmdtable = {}
1760 > cmdtable = {}
1761 > command = cmdutil.command(cmdtable)
1761 > command = cmdutil.command(cmdtable)
1762 > @command(b'foo', [], norepo=True)
1762 > @command(b'foo', [], norepo=True)
1763 > def foo(ui):
1763 > def foo(ui):
1764 > pass
1764 > pass
1765 > EOF
1765 > EOF
1766
1766
1767 Prohibit the use of unicode strings as the default value of options
1767 Prohibit the use of unicode strings as the default value of options
1768
1768
1769 $ hg init $TESTTMP/opt-unicode-default
1769 $ hg init $TESTTMP/opt-unicode-default
1770
1770
1771 $ cat > $TESTTMP/test_unicode_default_value.py << EOF
1771 $ cat > $TESTTMP/test_unicode_default_value.py << EOF
1772 > from __future__ import print_function
1772 > from __future__ import print_function
1773 > from mercurial import registrar
1773 > from mercurial import registrar
1774 > cmdtable = {}
1774 > cmdtable = {}
1775 > command = registrar.command(cmdtable)
1775 > command = registrar.command(cmdtable)
1776 > @command(b'dummy', [(b'', b'opt', u'value', u'help')], 'ext [OPTIONS]')
1776 > @command(b'dummy', [(b'', b'opt', u'value', u'help')], 'ext [OPTIONS]')
1777 > def ext(*args, **opts):
1777 > def ext(*args, **opts):
1778 > print(opts[b'opt'], flush=True)
1778 > print(opts[b'opt'], flush=True)
1779 > EOF
1779 > EOF
1780 $ $PYTHON $TESTTMP/unflush.py $TESTTMP/test_unicode_default_value.py
1780 $ $PYTHON $TESTTMP/unflush.py $TESTTMP/test_unicode_default_value.py
1781 $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
1781 $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
1782 > [extensions]
1782 > [extensions]
1783 > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
1783 > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
1784 > EOF
1784 > EOF
1785 $ hg -R $TESTTMP/opt-unicode-default dummy
1785 $ hg -R $TESTTMP/opt-unicode-default dummy
1786 *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: unicode *'value' found in cmdtable.dummy (glob)
1786 *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: unicode *'value' found in cmdtable.dummy (glob)
1787 *** (use b'' to make it byte string)
1787 *** (use b'' to make it byte string)
1788 hg: unknown command 'dummy'
1788 hg: unknown command 'dummy'
1789 (did you mean summary?)
1789 (did you mean summary?)
1790 [255]
1790 [255]
@@ -1,466 +1,466 b''
1 $ hg init a
1 $ hg init a
2 $ cd a
2 $ cd a
3 $ echo a > a
3 $ echo a > a
4 $ hg ci -A -d'1 0' -m a
4 $ hg ci -A -d'1 0' -m a
5 adding a
5 adding a
6
6
7 $ cd ..
7 $ cd ..
8
8
9 $ hg init b
9 $ hg init b
10 $ cd b
10 $ cd b
11 $ echo b > b
11 $ echo b > b
12 $ hg ci -A -d'1 0' -m b
12 $ hg ci -A -d'1 0' -m b
13 adding b
13 adding b
14
14
15 $ cd ..
15 $ cd ..
16
16
17 $ hg clone a c
17 $ hg clone a c
18 updating to branch default
18 updating to branch default
19 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
19 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 $ cd c
20 $ cd c
21 $ cat >> .hg/hgrc <<EOF
21 $ cat >> .hg/hgrc <<EOF
22 > [paths]
22 > [paths]
23 > relative = ../a
23 > relative = ../a
24 > EOF
24 > EOF
25 $ hg pull -f ../b
25 $ hg pull -f ../b
26 pulling from ../b
26 pulling from ../b
27 searching for changes
27 searching for changes
28 warning: repository is unrelated
28 warning: repository is unrelated
29 requesting all changes
29 requesting all changes
30 adding changesets
30 adding changesets
31 adding manifests
31 adding manifests
32 adding file changes
32 adding file changes
33 added 1 changesets with 1 changes to 1 files (+1 heads)
33 added 1 changesets with 1 changes to 1 files (+1 heads)
34 new changesets b6c483daf290
34 new changesets b6c483daf290
35 (run 'hg heads' to see heads, 'hg merge' to merge)
35 (run 'hg heads' to see heads, 'hg merge' to merge)
36 $ hg merge
36 $ hg merge
37 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 (branch merge, don't forget to commit)
38 (branch merge, don't forget to commit)
39
39
40 $ cd ..
40 $ cd ..
41
41
42 Testing -R/--repository:
42 Testing -R/--repository:
43
43
44 $ hg -R a tip
44 $ hg -R a tip
45 changeset: 0:8580ff50825a
45 changeset: 0:8580ff50825a
46 tag: tip
46 tag: tip
47 user: test
47 user: test
48 date: Thu Jan 01 00:00:01 1970 +0000
48 date: Thu Jan 01 00:00:01 1970 +0000
49 summary: a
49 summary: a
50
50
51 $ hg --repository b tip
51 $ hg --repository b tip
52 changeset: 0:b6c483daf290
52 changeset: 0:b6c483daf290
53 tag: tip
53 tag: tip
54 user: test
54 user: test
55 date: Thu Jan 01 00:00:01 1970 +0000
55 date: Thu Jan 01 00:00:01 1970 +0000
56 summary: b
56 summary: b
57
57
58
58
59 -R with a URL:
59 -R with a URL:
60
60
61 $ hg -R file:a identify
61 $ hg -R file:a identify
62 8580ff50825a tip
62 8580ff50825a tip
63 $ hg -R file://localhost/`pwd`/a/ identify
63 $ hg -R file://localhost/`pwd`/a/ identify
64 8580ff50825a tip
64 8580ff50825a tip
65
65
66 -R with path aliases:
66 -R with path aliases:
67
67
68 $ cd c
68 $ cd c
69 $ hg -R default identify
69 $ hg -R default identify
70 8580ff50825a tip
70 8580ff50825a tip
71 $ hg -R relative identify
71 $ hg -R relative identify
72 8580ff50825a tip
72 8580ff50825a tip
73 $ echo '[paths]' >> $HGRCPATH
73 $ echo '[paths]' >> $HGRCPATH
74 $ echo 'relativetohome = a' >> $HGRCPATH
74 $ echo 'relativetohome = a' >> $HGRCPATH
75 $ HOME=`pwd`/../ hg -R relativetohome identify
75 $ HOME=`pwd`/../ hg -R relativetohome identify
76 8580ff50825a tip
76 8580ff50825a tip
77 $ cd ..
77 $ cd ..
78
78
79 #if no-outer-repo
79 #if no-outer-repo
80
80
81 Implicit -R:
81 Implicit -R:
82
82
83 $ hg ann a/a
83 $ hg ann a/a
84 0: a
84 0: a
85 $ hg ann a/a a/a
85 $ hg ann a/a a/a
86 0: a
86 0: a
87 $ hg ann a/a b/b
87 $ hg ann a/a b/b
88 abort: no repository found in '$TESTTMP' (.hg not found)!
88 abort: no repository found in '$TESTTMP' (.hg not found)!
89 [255]
89 [255]
90 $ hg -R b ann a/a
90 $ hg -R b ann a/a
91 abort: a/a not under root '$TESTTMP/b'
91 abort: a/a not under root '$TESTTMP/b'
92 (consider using '--cwd b')
92 (consider using '--cwd b')
93 [255]
93 [255]
94 $ hg log
94 $ hg log
95 abort: no repository found in '$TESTTMP' (.hg not found)!
95 abort: no repository found in '$TESTTMP' (.hg not found)!
96 [255]
96 [255]
97
97
98 #endif
98 #endif
99
99
100 Abbreviation of long option:
100 Abbreviation of long option:
101
101
102 $ hg --repo c tip
102 $ hg --repo c tip
103 changeset: 1:b6c483daf290
103 changeset: 1:b6c483daf290
104 tag: tip
104 tag: tip
105 parent: -1:000000000000
105 parent: -1:000000000000
106 user: test
106 user: test
107 date: Thu Jan 01 00:00:01 1970 +0000
107 date: Thu Jan 01 00:00:01 1970 +0000
108 summary: b
108 summary: b
109
109
110
110
111 earlygetopt with duplicate options (36d23de02da1):
111 earlygetopt with duplicate options (36d23de02da1):
112
112
113 $ hg --cwd a --cwd b --cwd c tip
113 $ hg --cwd a --cwd b --cwd c tip
114 changeset: 1:b6c483daf290
114 changeset: 1:b6c483daf290
115 tag: tip
115 tag: tip
116 parent: -1:000000000000
116 parent: -1:000000000000
117 user: test
117 user: test
118 date: Thu Jan 01 00:00:01 1970 +0000
118 date: Thu Jan 01 00:00:01 1970 +0000
119 summary: b
119 summary: b
120
120
121 $ hg --repo c --repository b -R a tip
121 $ hg --repo c --repository b -R a tip
122 changeset: 0:8580ff50825a
122 changeset: 0:8580ff50825a
123 tag: tip
123 tag: tip
124 user: test
124 user: test
125 date: Thu Jan 01 00:00:01 1970 +0000
125 date: Thu Jan 01 00:00:01 1970 +0000
126 summary: a
126 summary: a
127
127
128
128
129 earlygetopt short option without following space:
129 earlygetopt short option without following space:
130
130
131 $ hg -q -Rb tip
131 $ hg -q -Rb tip
132 0:b6c483daf290
132 0:b6c483daf290
133
133
134 earlygetopt with illegal abbreviations:
134 earlygetopt with illegal abbreviations:
135
135
136 $ hg --confi "foo.bar=baz"
136 $ hg --confi "foo.bar=baz"
137 abort: option --config may not be abbreviated!
137 abort: option --config may not be abbreviated!
138 [255]
138 [255]
139 $ hg --cw a tip
139 $ hg --cw a tip
140 abort: option --cwd may not be abbreviated!
140 abort: option --cwd may not be abbreviated!
141 [255]
141 [255]
142 $ hg --rep a tip
142 $ hg --rep a tip
143 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
143 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
144 [255]
144 [255]
145 $ hg --repositor a tip
145 $ hg --repositor a tip
146 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
146 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
147 [255]
147 [255]
148 $ hg -qR a tip
148 $ hg -qR a tip
149 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
149 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
150 [255]
150 [255]
151 $ hg -qRa tip
151 $ hg -qRa tip
152 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
152 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
153 [255]
153 [255]
154
154
155 Testing --cwd:
155 Testing --cwd:
156
156
157 $ hg --cwd a parents
157 $ hg --cwd a parents
158 changeset: 0:8580ff50825a
158 changeset: 0:8580ff50825a
159 tag: tip
159 tag: tip
160 user: test
160 user: test
161 date: Thu Jan 01 00:00:01 1970 +0000
161 date: Thu Jan 01 00:00:01 1970 +0000
162 summary: a
162 summary: a
163
163
164
164
165 Testing -y/--noninteractive - just be sure it is parsed:
165 Testing -y/--noninteractive - just be sure it is parsed:
166
166
167 $ hg --cwd a tip -q --noninteractive
167 $ hg --cwd a tip -q --noninteractive
168 0:8580ff50825a
168 0:8580ff50825a
169 $ hg --cwd a tip -q -y
169 $ hg --cwd a tip -q -y
170 0:8580ff50825a
170 0:8580ff50825a
171
171
172 Testing -q/--quiet:
172 Testing -q/--quiet:
173
173
174 $ hg -R a -q tip
174 $ hg -R a -q tip
175 0:8580ff50825a
175 0:8580ff50825a
176 $ hg -R b -q tip
176 $ hg -R b -q tip
177 0:b6c483daf290
177 0:b6c483daf290
178 $ hg -R c --quiet parents
178 $ hg -R c --quiet parents
179 0:8580ff50825a
179 0:8580ff50825a
180 1:b6c483daf290
180 1:b6c483daf290
181
181
182 Testing -v/--verbose:
182 Testing -v/--verbose:
183
183
184 $ hg --cwd c head -v
184 $ hg --cwd c head -v
185 changeset: 1:b6c483daf290
185 changeset: 1:b6c483daf290
186 tag: tip
186 tag: tip
187 parent: -1:000000000000
187 parent: -1:000000000000
188 user: test
188 user: test
189 date: Thu Jan 01 00:00:01 1970 +0000
189 date: Thu Jan 01 00:00:01 1970 +0000
190 files: b
190 files: b
191 description:
191 description:
192 b
192 b
193
193
194
194
195 changeset: 0:8580ff50825a
195 changeset: 0:8580ff50825a
196 user: test
196 user: test
197 date: Thu Jan 01 00:00:01 1970 +0000
197 date: Thu Jan 01 00:00:01 1970 +0000
198 files: a
198 files: a
199 description:
199 description:
200 a
200 a
201
201
202
202
203 $ hg --cwd b tip --verbose
203 $ hg --cwd b tip --verbose
204 changeset: 0:b6c483daf290
204 changeset: 0:b6c483daf290
205 tag: tip
205 tag: tip
206 user: test
206 user: test
207 date: Thu Jan 01 00:00:01 1970 +0000
207 date: Thu Jan 01 00:00:01 1970 +0000
208 files: b
208 files: b
209 description:
209 description:
210 b
210 b
211
211
212
212
213
213
214 Testing --config:
214 Testing --config:
215
215
216 $ hg --cwd c --config paths.quuxfoo=bar paths | grep quuxfoo > /dev/null && echo quuxfoo
216 $ hg --cwd c --config paths.quuxfoo=bar paths | grep quuxfoo > /dev/null && echo quuxfoo
217 quuxfoo
217 quuxfoo
218 $ hg --cwd c --config '' tip -q
218 $ hg --cwd c --config '' tip -q
219 abort: malformed --config option: '' (use --config section.name=value)
219 abort: malformed --config option: '' (use --config section.name=value)
220 [255]
220 [255]
221 $ hg --cwd c --config a.b tip -q
221 $ hg --cwd c --config a.b tip -q
222 abort: malformed --config option: 'a.b' (use --config section.name=value)
222 abort: malformed --config option: 'a.b' (use --config section.name=value)
223 [255]
223 [255]
224 $ hg --cwd c --config a tip -q
224 $ hg --cwd c --config a tip -q
225 abort: malformed --config option: 'a' (use --config section.name=value)
225 abort: malformed --config option: 'a' (use --config section.name=value)
226 [255]
226 [255]
227 $ hg --cwd c --config a.= tip -q
227 $ hg --cwd c --config a.= tip -q
228 abort: malformed --config option: 'a.=' (use --config section.name=value)
228 abort: malformed --config option: 'a.=' (use --config section.name=value)
229 [255]
229 [255]
230 $ hg --cwd c --config .b= tip -q
230 $ hg --cwd c --config .b= tip -q
231 abort: malformed --config option: '.b=' (use --config section.name=value)
231 abort: malformed --config option: '.b=' (use --config section.name=value)
232 [255]
232 [255]
233
233
234 Testing --debug:
234 Testing --debug:
235
235
236 $ hg --cwd c log --debug
236 $ hg --cwd c log --debug
237 changeset: 1:b6c483daf2907ce5825c0bb50f5716226281cc1a
237 changeset: 1:b6c483daf2907ce5825c0bb50f5716226281cc1a
238 tag: tip
238 tag: tip
239 phase: public
239 phase: public
240 parent: -1:0000000000000000000000000000000000000000
240 parent: -1:0000000000000000000000000000000000000000
241 parent: -1:0000000000000000000000000000000000000000
241 parent: -1:0000000000000000000000000000000000000000
242 manifest: 1:23226e7a252cacdc2d99e4fbdc3653441056de49
242 manifest: 1:23226e7a252cacdc2d99e4fbdc3653441056de49
243 user: test
243 user: test
244 date: Thu Jan 01 00:00:01 1970 +0000
244 date: Thu Jan 01 00:00:01 1970 +0000
245 files+: b
245 files+: b
246 extra: branch=default
246 extra: branch=default
247 description:
247 description:
248 b
248 b
249
249
250
250
251 changeset: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab
251 changeset: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab
252 phase: public
252 phase: public
253 parent: -1:0000000000000000000000000000000000000000
253 parent: -1:0000000000000000000000000000000000000000
254 parent: -1:0000000000000000000000000000000000000000
254 parent: -1:0000000000000000000000000000000000000000
255 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
255 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
256 user: test
256 user: test
257 date: Thu Jan 01 00:00:01 1970 +0000
257 date: Thu Jan 01 00:00:01 1970 +0000
258 files+: a
258 files+: a
259 extra: branch=default
259 extra: branch=default
260 description:
260 description:
261 a
261 a
262
262
263
263
264
264
265 Testing --traceback:
265 Testing --traceback:
266
266
267 #if no-chg
267 #if no-chg
268 $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
268 $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
269 Traceback (most recent call last):
269 Traceback (most recent call last):
270 Traceback (most recent call last): (py3k !)
270 Traceback (most recent call last): (py3 !)
271 #else
271 #else
272 Traceback for '--config' errors not supported with chg.
272 Traceback for '--config' errors not supported with chg.
273 $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
273 $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
274 [1]
274 [1]
275 #endif
275 #endif
276
276
277 Testing --time:
277 Testing --time:
278
278
279 $ hg --cwd a --time id
279 $ hg --cwd a --time id
280 8580ff50825a tip
280 8580ff50825a tip
281 time: real * (glob)
281 time: real * (glob)
282
282
283 Testing --version:
283 Testing --version:
284
284
285 $ hg --version -q
285 $ hg --version -q
286 Mercurial Distributed SCM * (glob)
286 Mercurial Distributed SCM * (glob)
287
287
288 hide outer repo
288 hide outer repo
289 $ hg init
289 $ hg init
290
290
291 Testing -h/--help:
291 Testing -h/--help:
292
292
293 #if no-extraextensions
293 #if no-extraextensions
294
294
295 $ hg -h
295 $ hg -h
296 Mercurial Distributed SCM
296 Mercurial Distributed SCM
297
297
298 list of commands:
298 list of commands:
299
299
300 add add the specified files on the next commit
300 add add the specified files on the next commit
301 addremove add all new files, delete all missing files
301 addremove add all new files, delete all missing files
302 annotate show changeset information by line for each file
302 annotate show changeset information by line for each file
303 archive create an unversioned archive of a repository revision
303 archive create an unversioned archive of a repository revision
304 backout reverse effect of earlier changeset
304 backout reverse effect of earlier changeset
305 bisect subdivision search of changesets
305 bisect subdivision search of changesets
306 bookmarks create a new bookmark or list existing bookmarks
306 bookmarks create a new bookmark or list existing bookmarks
307 branch set or show the current branch name
307 branch set or show the current branch name
308 branches list repository named branches
308 branches list repository named branches
309 bundle create a bundle file
309 bundle create a bundle file
310 cat output the current or given revision of files
310 cat output the current or given revision of files
311 clone make a copy of an existing repository
311 clone make a copy of an existing repository
312 commit commit the specified files or all outstanding changes
312 commit commit the specified files or all outstanding changes
313 config show combined config settings from all hgrc files
313 config show combined config settings from all hgrc files
314 copy mark files as copied for the next commit
314 copy mark files as copied for the next commit
315 diff diff repository (or selected files)
315 diff diff repository (or selected files)
316 export dump the header and diffs for one or more changesets
316 export dump the header and diffs for one or more changesets
317 files list tracked files
317 files list tracked files
318 forget forget the specified files on the next commit
318 forget forget the specified files on the next commit
319 graft copy changes from other branches onto the current branch
319 graft copy changes from other branches onto the current branch
320 grep search revision history for a pattern in specified files
320 grep search revision history for a pattern in specified files
321 heads show branch heads
321 heads show branch heads
322 help show help for a given topic or a help overview
322 help show help for a given topic or a help overview
323 identify identify the working directory or specified revision
323 identify identify the working directory or specified revision
324 import import an ordered set of patches
324 import import an ordered set of patches
325 incoming show new changesets found in source
325 incoming show new changesets found in source
326 init create a new repository in the given directory
326 init create a new repository in the given directory
327 log show revision history of entire repository or files
327 log show revision history of entire repository or files
328 manifest output the current or given revision of the project manifest
328 manifest output the current or given revision of the project manifest
329 merge merge another revision into working directory
329 merge merge another revision into working directory
330 outgoing show changesets not found in the destination
330 outgoing show changesets not found in the destination
331 paths show aliases for remote repositories
331 paths show aliases for remote repositories
332 phase set or show the current phase name
332 phase set or show the current phase name
333 pull pull changes from the specified source
333 pull pull changes from the specified source
334 push push changes to the specified destination
334 push push changes to the specified destination
335 recover roll back an interrupted transaction
335 recover roll back an interrupted transaction
336 remove remove the specified files on the next commit
336 remove remove the specified files on the next commit
337 rename rename files; equivalent of copy + remove
337 rename rename files; equivalent of copy + remove
338 resolve redo merges or set/view the merge status of files
338 resolve redo merges or set/view the merge status of files
339 revert restore files to their checkout state
339 revert restore files to their checkout state
340 root print the root (top) of the current working directory
340 root print the root (top) of the current working directory
341 serve start stand-alone webserver
341 serve start stand-alone webserver
342 status show changed files in the working directory
342 status show changed files in the working directory
343 summary summarize working directory state
343 summary summarize working directory state
344 tag add one or more tags for the current or given revision
344 tag add one or more tags for the current or given revision
345 tags list repository tags
345 tags list repository tags
346 unbundle apply one or more bundle files
346 unbundle apply one or more bundle files
347 update update working directory (or switch revisions)
347 update update working directory (or switch revisions)
348 verify verify the integrity of the repository
348 verify verify the integrity of the repository
349 version output version and copyright information
349 version output version and copyright information
350
350
351 additional help topics:
351 additional help topics:
352
352
353 bundlespec Bundle File Formats
353 bundlespec Bundle File Formats
354 color Colorizing Outputs
354 color Colorizing Outputs
355 config Configuration Files
355 config Configuration Files
356 dates Date Formats
356 dates Date Formats
357 deprecated Deprecated Features
357 deprecated Deprecated Features
358 diffs Diff Formats
358 diffs Diff Formats
359 environment Environment Variables
359 environment Environment Variables
360 extensions Using Additional Features
360 extensions Using Additional Features
361 filesets Specifying File Sets
361 filesets Specifying File Sets
362 flags Command-line flags
362 flags Command-line flags
363 glossary Glossary
363 glossary Glossary
364 hgignore Syntax for Mercurial Ignore Files
364 hgignore Syntax for Mercurial Ignore Files
365 hgweb Configuring hgweb
365 hgweb Configuring hgweb
366 internals Technical implementation topics
366 internals Technical implementation topics
367 merge-tools Merge Tools
367 merge-tools Merge Tools
368 pager Pager Support
368 pager Pager Support
369 patterns File Name Patterns
369 patterns File Name Patterns
370 phases Working with Phases
370 phases Working with Phases
371 revisions Specifying Revisions
371 revisions Specifying Revisions
372 scripting Using Mercurial from scripts and automation
372 scripting Using Mercurial from scripts and automation
373 subrepos Subrepositories
373 subrepos Subrepositories
374 templating Template Usage
374 templating Template Usage
375 urls URL Paths
375 urls URL Paths
376
376
377 (use 'hg help -v' to show built-in aliases and global options)
377 (use 'hg help -v' to show built-in aliases and global options)
378
378
379 $ hg --help
379 $ hg --help
380 Mercurial Distributed SCM
380 Mercurial Distributed SCM
381
381
382 list of commands:
382 list of commands:
383
383
384 add add the specified files on the next commit
384 add add the specified files on the next commit
385 addremove add all new files, delete all missing files
385 addremove add all new files, delete all missing files
386 annotate show changeset information by line for each file
386 annotate show changeset information by line for each file
387 archive create an unversioned archive of a repository revision
387 archive create an unversioned archive of a repository revision
388 backout reverse effect of earlier changeset
388 backout reverse effect of earlier changeset
389 bisect subdivision search of changesets
389 bisect subdivision search of changesets
390 bookmarks create a new bookmark or list existing bookmarks
390 bookmarks create a new bookmark or list existing bookmarks
391 branch set or show the current branch name
391 branch set or show the current branch name
392 branches list repository named branches
392 branches list repository named branches
393 bundle create a bundle file
393 bundle create a bundle file
394 cat output the current or given revision of files
394 cat output the current or given revision of files
395 clone make a copy of an existing repository
395 clone make a copy of an existing repository
396 commit commit the specified files or all outstanding changes
396 commit commit the specified files or all outstanding changes
397 config show combined config settings from all hgrc files
397 config show combined config settings from all hgrc files
398 copy mark files as copied for the next commit
398 copy mark files as copied for the next commit
399 diff diff repository (or selected files)
399 diff diff repository (or selected files)
400 export dump the header and diffs for one or more changesets
400 export dump the header and diffs for one or more changesets
401 files list tracked files
401 files list tracked files
402 forget forget the specified files on the next commit
402 forget forget the specified files on the next commit
403 graft copy changes from other branches onto the current branch
403 graft copy changes from other branches onto the current branch
404 grep search revision history for a pattern in specified files
404 grep search revision history for a pattern in specified files
405 heads show branch heads
405 heads show branch heads
406 help show help for a given topic or a help overview
406 help show help for a given topic or a help overview
407 identify identify the working directory or specified revision
407 identify identify the working directory or specified revision
408 import import an ordered set of patches
408 import import an ordered set of patches
409 incoming show new changesets found in source
409 incoming show new changesets found in source
410 init create a new repository in the given directory
410 init create a new repository in the given directory
411 log show revision history of entire repository or files
411 log show revision history of entire repository or files
412 manifest output the current or given revision of the project manifest
412 manifest output the current or given revision of the project manifest
413 merge merge another revision into working directory
413 merge merge another revision into working directory
414 outgoing show changesets not found in the destination
414 outgoing show changesets not found in the destination
415 paths show aliases for remote repositories
415 paths show aliases for remote repositories
416 phase set or show the current phase name
416 phase set or show the current phase name
417 pull pull changes from the specified source
417 pull pull changes from the specified source
418 push push changes to the specified destination
418 push push changes to the specified destination
419 recover roll back an interrupted transaction
419 recover roll back an interrupted transaction
420 remove remove the specified files on the next commit
420 remove remove the specified files on the next commit
421 rename rename files; equivalent of copy + remove
421 rename rename files; equivalent of copy + remove
422 resolve redo merges or set/view the merge status of files
422 resolve redo merges or set/view the merge status of files
423 revert restore files to their checkout state
423 revert restore files to their checkout state
424 root print the root (top) of the current working directory
424 root print the root (top) of the current working directory
425 serve start stand-alone webserver
425 serve start stand-alone webserver
426 status show changed files in the working directory
426 status show changed files in the working directory
427 summary summarize working directory state
427 summary summarize working directory state
428 tag add one or more tags for the current or given revision
428 tag add one or more tags for the current or given revision
429 tags list repository tags
429 tags list repository tags
430 unbundle apply one or more bundle files
430 unbundle apply one or more bundle files
431 update update working directory (or switch revisions)
431 update update working directory (or switch revisions)
432 verify verify the integrity of the repository
432 verify verify the integrity of the repository
433 version output version and copyright information
433 version output version and copyright information
434
434
435 additional help topics:
435 additional help topics:
436
436
437 bundlespec Bundle File Formats
437 bundlespec Bundle File Formats
438 color Colorizing Outputs
438 color Colorizing Outputs
439 config Configuration Files
439 config Configuration Files
440 dates Date Formats
440 dates Date Formats
441 deprecated Deprecated Features
441 deprecated Deprecated Features
442 diffs Diff Formats
442 diffs Diff Formats
443 environment Environment Variables
443 environment Environment Variables
444 extensions Using Additional Features
444 extensions Using Additional Features
445 filesets Specifying File Sets
445 filesets Specifying File Sets
446 flags Command-line flags
446 flags Command-line flags
447 glossary Glossary
447 glossary Glossary
448 hgignore Syntax for Mercurial Ignore Files
448 hgignore Syntax for Mercurial Ignore Files
449 hgweb Configuring hgweb
449 hgweb Configuring hgweb
450 internals Technical implementation topics
450 internals Technical implementation topics
451 merge-tools Merge Tools
451 merge-tools Merge Tools
452 pager Pager Support
452 pager Pager Support
453 patterns File Name Patterns
453 patterns File Name Patterns
454 phases Working with Phases
454 phases Working with Phases
455 revisions Specifying Revisions
455 revisions Specifying Revisions
456 scripting Using Mercurial from scripts and automation
456 scripting Using Mercurial from scripts and automation
457 subrepos Subrepositories
457 subrepos Subrepositories
458 templating Template Usage
458 templating Template Usage
459 urls URL Paths
459 urls URL Paths
460
460
461 (use 'hg help -v' to show built-in aliases and global options)
461 (use 'hg help -v' to show built-in aliases and global options)
462
462
463 #endif
463 #endif
464
464
465 Not tested: --debugger
465 Not tested: --debugger
466
466
General Comments 0
You need to be logged in to leave comments. Login now