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