##// END OF EJS Templates
py3: convert system strings to bytes in doctest of formatter.py
Yuya Nishihara -
r34257:ebe3d009 default
parent child Browse files
Show More
@@ -1,533 +1,534 b''
1 1 # formatter.py - generic output formatting for mercurial
2 2 #
3 3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Generic output formatting for Mercurial
9 9
10 10 The formatter provides API to show data in various ways. The following
11 11 functions should be used in place of ui.write():
12 12
13 13 - fm.write() for unconditional output
14 14 - fm.condwrite() to show some extra data conditionally in plain output
15 15 - fm.context() to provide changectx to template output
16 16 - fm.data() to provide extra data to JSON or template output
17 17 - fm.plain() to show raw text that isn't provided to JSON or template output
18 18
19 19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 20 beforehand so the data is converted to the appropriate data type. Use
21 21 fm.isplain() if you need to convert or format data conditionally which isn't
22 22 supported by the formatter API.
23 23
24 24 To build nested structure (i.e. a list of dicts), use fm.nested().
25 25
26 26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27 27
28 28 fm.condwrite() vs 'if cond:':
29 29
30 30 In most cases, use fm.condwrite() so users can selectively show the data
31 31 in template output. If it's costly to build data, use plain 'if cond:' with
32 32 fm.write().
33 33
34 34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35 35
36 36 fm.nested() should be used to form a tree structure (a list of dicts of
37 37 lists of dicts...) which can be accessed through template keywords, e.g.
38 38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 39 exports a dict-type object to template, which can be accessed by e.g.
40 40 "{get(foo, key)}" function.
41 41
42 42 Doctest helper:
43 43
44 44 >>> def show(fn, verbose=False, **opts):
45 45 ... import sys
46 46 ... from . import ui as uimod
47 47 ... ui = uimod.ui()
48 48 ... ui.verbose = verbose
49 49 ... ui.pushbuffer()
50 50 ... try:
51 ... return fn(ui, ui.formatter(fn.__name__, opts))
51 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
52 ... pycompat.byteskwargs(opts)))
52 53 ... finally:
53 54 ... print(pycompat.sysstr(ui.popbuffer()), end='')
54 55
55 56 Basic example:
56 57
57 58 >>> def files(ui, fm):
58 59 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
59 60 ... for f in files:
60 61 ... fm.startitem()
61 62 ... fm.write(b'path', b'%s', f[0])
62 63 ... fm.condwrite(ui.verbose, b'date', b' %s',
63 64 ... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
64 65 ... fm.data(size=f[1])
65 66 ... fm.plain(b'\\n')
66 67 ... fm.end()
67 68 >>> show(files)
68 69 foo
69 70 bar
70 71 >>> show(files, verbose=True)
71 72 foo 1970-01-01 00:00:00
72 73 bar 1970-01-01 00:00:01
73 74 >>> show(files, template=b'json')
74 75 [
75 76 {
76 77 "date": [0, 0],
77 78 "path": "foo",
78 79 "size": 123
79 80 },
80 81 {
81 82 "date": [1, 0],
82 83 "path": "bar",
83 84 "size": 456
84 85 }
85 86 ]
86 87 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
87 88 path: foo
88 89 date: 1970-01-01T00:00:00+00:00
89 90 path: bar
90 91 date: 1970-01-01T00:00:01+00:00
91 92
92 93 Nested example:
93 94
94 95 >>> def subrepos(ui, fm):
95 96 ... fm.startitem()
96 97 ... fm.write(b'repo', b'[%s]\\n', b'baz')
97 98 ... files(ui, fm.nested(b'files'))
98 99 ... fm.end()
99 100 >>> show(subrepos)
100 101 [baz]
101 102 foo
102 103 bar
103 104 >>> show(subrepos, template=b'{repo}: {join(files % "{path}", ", ")}\\n')
104 105 baz: foo, bar
105 106 """
106 107
107 108 from __future__ import absolute_import, print_function
108 109
109 110 import collections
110 111 import contextlib
111 112 import itertools
112 113 import os
113 114
114 115 from .i18n import _
115 116 from .node import (
116 117 hex,
117 118 short,
118 119 )
119 120
120 121 from . import (
121 122 error,
122 123 pycompat,
123 124 templatefilters,
124 125 templatekw,
125 126 templater,
126 127 util,
127 128 )
128 129
129 130 pickle = util.pickle
130 131
131 132 class _nullconverter(object):
132 133 '''convert non-primitive data types to be processed by formatter'''
133 134
134 135 # set to True if context object should be stored as item
135 136 storecontext = False
136 137
137 138 @staticmethod
138 139 def formatdate(date, fmt):
139 140 '''convert date tuple to appropriate format'''
140 141 return date
141 142 @staticmethod
142 143 def formatdict(data, key, value, fmt, sep):
143 144 '''convert dict or key-value pairs to appropriate dict format'''
144 145 # use plain dict instead of util.sortdict so that data can be
145 146 # serialized as a builtin dict in pickle output
146 147 return dict(data)
147 148 @staticmethod
148 149 def formatlist(data, name, fmt, sep):
149 150 '''convert iterable to appropriate list format'''
150 151 return list(data)
151 152
152 153 class baseformatter(object):
153 154 def __init__(self, ui, topic, opts, converter):
154 155 self._ui = ui
155 156 self._topic = topic
156 157 self._style = opts.get("style")
157 158 self._template = opts.get("template")
158 159 self._converter = converter
159 160 self._item = None
160 161 # function to convert node to string suitable for this output
161 162 self.hexfunc = hex
162 163 def __enter__(self):
163 164 return self
164 165 def __exit__(self, exctype, excvalue, traceback):
165 166 if exctype is None:
166 167 self.end()
167 168 def _showitem(self):
168 169 '''show a formatted item once all data is collected'''
169 170 pass
170 171 def startitem(self):
171 172 '''begin an item in the format list'''
172 173 if self._item is not None:
173 174 self._showitem()
174 175 self._item = {}
175 176 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
176 177 '''convert date tuple to appropriate format'''
177 178 return self._converter.formatdate(date, fmt)
178 179 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
179 180 '''convert dict or key-value pairs to appropriate dict format'''
180 181 return self._converter.formatdict(data, key, value, fmt, sep)
181 182 def formatlist(self, data, name, fmt='%s', sep=' '):
182 183 '''convert iterable to appropriate list format'''
183 184 # name is mandatory argument for now, but it could be optional if
184 185 # we have default template keyword, e.g. {item}
185 186 return self._converter.formatlist(data, name, fmt, sep)
186 187 def context(self, **ctxs):
187 188 '''insert context objects to be used to render template keywords'''
188 189 ctxs = pycompat.byteskwargs(ctxs)
189 190 assert all(k == 'ctx' for k in ctxs)
190 191 if self._converter.storecontext:
191 192 self._item.update(ctxs)
192 193 def data(self, **data):
193 194 '''insert data into item that's not shown in default output'''
194 195 data = pycompat.byteskwargs(data)
195 196 self._item.update(data)
196 197 def write(self, fields, deftext, *fielddata, **opts):
197 198 '''do default text output while assigning data to item'''
198 199 fieldkeys = fields.split()
199 200 assert len(fieldkeys) == len(fielddata)
200 201 self._item.update(zip(fieldkeys, fielddata))
201 202 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
202 203 '''do conditional write (primarily for plain formatter)'''
203 204 fieldkeys = fields.split()
204 205 assert len(fieldkeys) == len(fielddata)
205 206 self._item.update(zip(fieldkeys, fielddata))
206 207 def plain(self, text, **opts):
207 208 '''show raw text for non-templated mode'''
208 209 pass
209 210 def isplain(self):
210 211 '''check for plain formatter usage'''
211 212 return False
212 213 def nested(self, field):
213 214 '''sub formatter to store nested data in the specified field'''
214 215 self._item[field] = data = []
215 216 return _nestedformatter(self._ui, self._converter, data)
216 217 def end(self):
217 218 '''end output for the formatter'''
218 219 if self._item is not None:
219 220 self._showitem()
220 221
221 222 def nullformatter(ui, topic):
222 223 '''formatter that prints nothing'''
223 224 return baseformatter(ui, topic, opts={}, converter=_nullconverter)
224 225
225 226 class _nestedformatter(baseformatter):
226 227 '''build sub items and store them in the parent formatter'''
227 228 def __init__(self, ui, converter, data):
228 229 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
229 230 self._data = data
230 231 def _showitem(self):
231 232 self._data.append(self._item)
232 233
233 234 def _iteritems(data):
234 235 '''iterate key-value pairs in stable order'''
235 236 if isinstance(data, dict):
236 237 return sorted(data.iteritems())
237 238 return data
238 239
239 240 class _plainconverter(object):
240 241 '''convert non-primitive data types to text'''
241 242
242 243 storecontext = False
243 244
244 245 @staticmethod
245 246 def formatdate(date, fmt):
246 247 '''stringify date tuple in the given format'''
247 248 return util.datestr(date, fmt)
248 249 @staticmethod
249 250 def formatdict(data, key, value, fmt, sep):
250 251 '''stringify key-value pairs separated by sep'''
251 252 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
252 253 @staticmethod
253 254 def formatlist(data, name, fmt, sep):
254 255 '''stringify iterable separated by sep'''
255 256 return sep.join(fmt % e for e in data)
256 257
257 258 class plainformatter(baseformatter):
258 259 '''the default text output scheme'''
259 260 def __init__(self, ui, out, topic, opts):
260 261 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
261 262 if ui.debugflag:
262 263 self.hexfunc = hex
263 264 else:
264 265 self.hexfunc = short
265 266 if ui is out:
266 267 self._write = ui.write
267 268 else:
268 269 self._write = lambda s, **opts: out.write(s)
269 270 def startitem(self):
270 271 pass
271 272 def data(self, **data):
272 273 pass
273 274 def write(self, fields, deftext, *fielddata, **opts):
274 275 self._write(deftext % fielddata, **opts)
275 276 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
276 277 '''do conditional write'''
277 278 if cond:
278 279 self._write(deftext % fielddata, **opts)
279 280 def plain(self, text, **opts):
280 281 self._write(text, **opts)
281 282 def isplain(self):
282 283 return True
283 284 def nested(self, field):
284 285 # nested data will be directly written to ui
285 286 return self
286 287 def end(self):
287 288 pass
288 289
289 290 class debugformatter(baseformatter):
290 291 def __init__(self, ui, out, topic, opts):
291 292 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
292 293 self._out = out
293 294 self._out.write("%s = [\n" % self._topic)
294 295 def _showitem(self):
295 296 self._out.write(" " + repr(self._item) + ",\n")
296 297 def end(self):
297 298 baseformatter.end(self)
298 299 self._out.write("]\n")
299 300
300 301 class pickleformatter(baseformatter):
301 302 def __init__(self, ui, out, topic, opts):
302 303 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
303 304 self._out = out
304 305 self._data = []
305 306 def _showitem(self):
306 307 self._data.append(self._item)
307 308 def end(self):
308 309 baseformatter.end(self)
309 310 self._out.write(pickle.dumps(self._data))
310 311
311 312 class jsonformatter(baseformatter):
312 313 def __init__(self, ui, out, topic, opts):
313 314 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
314 315 self._out = out
315 316 self._out.write("[")
316 317 self._first = True
317 318 def _showitem(self):
318 319 if self._first:
319 320 self._first = False
320 321 else:
321 322 self._out.write(",")
322 323
323 324 self._out.write("\n {\n")
324 325 first = True
325 326 for k, v in sorted(self._item.items()):
326 327 if first:
327 328 first = False
328 329 else:
329 330 self._out.write(",\n")
330 331 u = templatefilters.json(v, paranoid=False)
331 332 self._out.write(' "%s": %s' % (k, u))
332 333 self._out.write("\n }")
333 334 def end(self):
334 335 baseformatter.end(self)
335 336 self._out.write("\n]\n")
336 337
337 338 class _templateconverter(object):
338 339 '''convert non-primitive data types to be processed by templater'''
339 340
340 341 storecontext = True
341 342
342 343 @staticmethod
343 344 def formatdate(date, fmt):
344 345 '''return date tuple'''
345 346 return date
346 347 @staticmethod
347 348 def formatdict(data, key, value, fmt, sep):
348 349 '''build object that can be evaluated as either plain string or dict'''
349 350 data = util.sortdict(_iteritems(data))
350 351 def f():
351 352 yield _plainconverter.formatdict(data, key, value, fmt, sep)
352 353 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
353 354 gen=f())
354 355 @staticmethod
355 356 def formatlist(data, name, fmt, sep):
356 357 '''build object that can be evaluated as either plain string or list'''
357 358 data = list(data)
358 359 def f():
359 360 yield _plainconverter.formatlist(data, name, fmt, sep)
360 361 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
361 362
362 363 class templateformatter(baseformatter):
363 364 def __init__(self, ui, out, topic, opts):
364 365 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
365 366 self._out = out
366 367 spec = lookuptemplate(ui, topic, opts.get('template', ''))
367 368 self._tref = spec.ref
368 369 self._t = loadtemplater(ui, spec, cache=templatekw.defaulttempl)
369 370 self._parts = templatepartsmap(spec, self._t,
370 371 ['docheader', 'docfooter', 'separator'])
371 372 self._counter = itertools.count()
372 373 self._cache = {} # for templatekw/funcs to store reusable data
373 374 self._renderitem('docheader', {})
374 375
375 376 def _showitem(self):
376 377 item = self._item.copy()
377 378 item['index'] = index = next(self._counter)
378 379 if index > 0:
379 380 self._renderitem('separator', {})
380 381 self._renderitem(self._tref, item)
381 382
382 383 def _renderitem(self, part, item):
383 384 if part not in self._parts:
384 385 return
385 386 ref = self._parts[part]
386 387
387 388 # TODO: add support for filectx. probably each template keyword or
388 389 # function will have to declare dependent resources. e.g.
389 390 # @templatekeyword(..., requires=('ctx',))
390 391 props = {}
391 392 if 'ctx' in item:
392 393 props.update(templatekw.keywords)
393 394 # explicitly-defined fields precede templatekw
394 395 props.update(item)
395 396 if 'ctx' in item:
396 397 # but template resources must be always available
397 398 props['templ'] = self._t
398 399 props['repo'] = props['ctx'].repo()
399 400 props['revcache'] = {}
400 401 props = pycompat.strkwargs(props)
401 402 g = self._t(ref, ui=self._ui, cache=self._cache, **props)
402 403 self._out.write(templater.stringify(g))
403 404
404 405 def end(self):
405 406 baseformatter.end(self)
406 407 self._renderitem('docfooter', {})
407 408
408 409 templatespec = collections.namedtuple(r'templatespec',
409 410 r'ref tmpl mapfile')
410 411
411 412 def lookuptemplate(ui, topic, tmpl):
412 413 """Find the template matching the given -T/--template spec 'tmpl'
413 414
414 415 'tmpl' can be any of the following:
415 416
416 417 - a literal template (e.g. '{rev}')
417 418 - a map-file name or path (e.g. 'changelog')
418 419 - a reference to [templates] in config file
419 420 - a path to raw template file
420 421
421 422 A map file defines a stand-alone template environment. If a map file
422 423 selected, all templates defined in the file will be loaded, and the
423 424 template matching the given topic will be rendered. No aliases will be
424 425 loaded from user config.
425 426
426 427 If no map file selected, all templates in [templates] section will be
427 428 available as well as aliases in [templatealias].
428 429 """
429 430
430 431 # looks like a literal template?
431 432 if '{' in tmpl:
432 433 return templatespec('', tmpl, None)
433 434
434 435 # perhaps a stock style?
435 436 if not os.path.split(tmpl)[0]:
436 437 mapname = (templater.templatepath('map-cmdline.' + tmpl)
437 438 or templater.templatepath(tmpl))
438 439 if mapname and os.path.isfile(mapname):
439 440 return templatespec(topic, None, mapname)
440 441
441 442 # perhaps it's a reference to [templates]
442 443 if ui.config('templates', tmpl):
443 444 return templatespec(tmpl, None, None)
444 445
445 446 if tmpl == 'list':
446 447 ui.write(_("available styles: %s\n") % templater.stylelist())
447 448 raise error.Abort(_("specify a template"))
448 449
449 450 # perhaps it's a path to a map or a template
450 451 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
451 452 # is it a mapfile for a style?
452 453 if os.path.basename(tmpl).startswith("map-"):
453 454 return templatespec(topic, None, os.path.realpath(tmpl))
454 455 with util.posixfile(tmpl, 'rb') as f:
455 456 tmpl = f.read()
456 457 return templatespec('', tmpl, None)
457 458
458 459 # constant string?
459 460 return templatespec('', tmpl, None)
460 461
461 462 def templatepartsmap(spec, t, partnames):
462 463 """Create a mapping of {part: ref}"""
463 464 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
464 465 if spec.mapfile:
465 466 partsmap.update((p, p) for p in partnames if p in t)
466 467 elif spec.ref:
467 468 for part in partnames:
468 469 ref = '%s:%s' % (spec.ref, part) # select config sub-section
469 470 if ref in t:
470 471 partsmap[part] = ref
471 472 return partsmap
472 473
473 474 def loadtemplater(ui, spec, cache=None):
474 475 """Create a templater from either a literal template or loading from
475 476 a map file"""
476 477 assert not (spec.tmpl and spec.mapfile)
477 478 if spec.mapfile:
478 479 return templater.templater.frommapfile(spec.mapfile, cache=cache)
479 480 return maketemplater(ui, spec.tmpl, cache=cache)
480 481
481 482 def maketemplater(ui, tmpl, cache=None):
482 483 """Create a templater from a string template 'tmpl'"""
483 484 aliases = ui.configitems('templatealias')
484 485 t = templater.templater(cache=cache, aliases=aliases)
485 486 t.cache.update((k, templater.unquotestring(v))
486 487 for k, v in ui.configitems('templates'))
487 488 if tmpl:
488 489 t.cache[''] = tmpl
489 490 return t
490 491
491 492 def formatter(ui, out, topic, opts):
492 493 template = opts.get("template", "")
493 494 if template == "json":
494 495 return jsonformatter(ui, out, topic, opts)
495 496 elif template == "pickle":
496 497 return pickleformatter(ui, out, topic, opts)
497 498 elif template == "debug":
498 499 return debugformatter(ui, out, topic, opts)
499 500 elif template != "":
500 501 return templateformatter(ui, out, topic, opts)
501 502 # developer config: ui.formatdebug
502 503 elif ui.configbool('ui', 'formatdebug'):
503 504 return debugformatter(ui, out, topic, opts)
504 505 # deprecated config: ui.formatjson
505 506 elif ui.configbool('ui', 'formatjson'):
506 507 return jsonformatter(ui, out, topic, opts)
507 508 return plainformatter(ui, out, topic, opts)
508 509
509 510 @contextlib.contextmanager
510 511 def openformatter(ui, filename, topic, opts):
511 512 """Create a formatter that writes outputs to the specified file
512 513
513 514 Must be invoked using the 'with' statement.
514 515 """
515 516 with util.posixfile(filename, 'wb') as out:
516 517 with formatter(ui, out, topic, opts) as fm:
517 518 yield fm
518 519
519 520 @contextlib.contextmanager
520 521 def _neverending(fm):
521 522 yield fm
522 523
523 524 def maybereopen(fm, filename, opts):
524 525 """Create a formatter backed by file if filename specified, else return
525 526 the given formatter
526 527
527 528 Must be invoked using the 'with' statement. This will never call fm.end()
528 529 of the given formatter.
529 530 """
530 531 if filename:
531 532 return openformatter(fm._ui, filename, fm._topic, opts)
532 533 else:
533 534 return _neverending(fm)
@@ -1,81 +1,81 b''
1 1 # this is hack to make sure no escape characters are inserted into the output
2 2
3 3 from __future__ import absolute_import
4 4
5 5 import doctest
6 6 import os
7 7 import re
8 8 import sys
9 9
10 10 ispy3 = (sys.version_info[0] >= 3)
11 11
12 12 if 'TERM' in os.environ:
13 13 del os.environ['TERM']
14 14
15 15 class py3docchecker(doctest.OutputChecker):
16 16 def check_output(self, want, got, optionflags):
17 17 want2 = re.sub(r'''\bu(['"])(.*?)\1''', r'\1\2\1', want) # py2: u''
18 18 got2 = re.sub(r'''\bb(['"])(.*?)\1''', r'\1\2\1', got) # py3: b''
19 19 # py3: <exc.name>: b'<msg>' -> <name>: <msg>
20 20 # <exc.name>: <others> -> <name>: <others>
21 21 got2 = re.sub(r'''^mercurial\.\w+\.(\w+): (['"])(.*?)\2''', r'\1: \3',
22 22 got2, re.MULTILINE)
23 23 got2 = re.sub(r'^mercurial\.\w+\.(\w+): ', r'\1: ', got2, re.MULTILINE)
24 24 return any(doctest.OutputChecker.check_output(self, w, g, optionflags)
25 25 for w, g in [(want, got), (want2, got2)])
26 26
27 27 # TODO: migrate doctests to py3 and enable them on both versions
28 28 def testmod(name, optionflags=0, testtarget=None, py2=True, py3=True):
29 29 if not (not ispy3 and py2 or ispy3 and py3):
30 30 return
31 31 __import__(name)
32 32 mod = sys.modules[name]
33 33 if testtarget is not None:
34 34 mod = getattr(mod, testtarget)
35 35
36 36 # minimal copy of doctest.testmod()
37 37 finder = doctest.DocTestFinder()
38 38 checker = None
39 39 if ispy3:
40 40 checker = py3docchecker()
41 41 runner = doctest.DocTestRunner(checker=checker, optionflags=optionflags)
42 42 for test in finder.find(mod, name):
43 43 runner.run(test)
44 44 runner.summarize()
45 45
46 46 testmod('mercurial.changegroup')
47 47 testmod('mercurial.changelog')
48 48 testmod('mercurial.color')
49 49 testmod('mercurial.config')
50 50 testmod('mercurial.context')
51 51 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
52 52 testmod('mercurial.dispatch')
53 53 testmod('mercurial.encoding')
54 testmod('mercurial.formatter', py3=False) # py3: write bytes to stdout
54 testmod('mercurial.formatter')
55 55 testmod('mercurial.hg')
56 56 testmod('mercurial.hgweb.hgwebdir_mod', py3=False) # py3: repr(bytes) ?
57 57 testmod('mercurial.match')
58 58 testmod('mercurial.mdiff')
59 59 testmod('mercurial.minirst')
60 60 testmod('mercurial.patch')
61 61 testmod('mercurial.pathutil')
62 62 testmod('mercurial.parser')
63 63 testmod('mercurial.pycompat')
64 64 testmod('mercurial.revsetlang')
65 65 testmod('mercurial.smartset')
66 66 testmod('mercurial.store')
67 67 testmod('mercurial.subrepo')
68 68 testmod('mercurial.templatefilters')
69 69 testmod('mercurial.templater')
70 70 testmod('mercurial.ui')
71 71 testmod('mercurial.url')
72 72 testmod('mercurial.util', py3=False) # py3: multiple bytes/unicode issues
73 73 testmod('mercurial.util', testtarget='platform')
74 74 testmod('hgext.convert.convcmd', py3=False) # py3: use of str() ?
75 75 testmod('hgext.convert.cvsps')
76 76 testmod('hgext.convert.filemap')
77 77 testmod('hgext.convert.p4')
78 78 testmod('hgext.convert.subversion')
79 79 testmod('hgext.mq')
80 80 # Helper scripts in tests/ that have doctests:
81 81 testmod('drawdag')
General Comments 0
You need to be logged in to leave comments. Login now