##// END OF EJS Templates
tests: handle .t files without trailing LF...
Mads Kiilerich -
r12934:ea7ad8c3 stable
parent child Browse files
Show More
@@ -1,1092 +1,1092 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 re
56 56
57 57 closefds = os.name == 'posix'
58 58 def Popen4(cmd, bufsize=-1):
59 59 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
60 60 close_fds=closefds,
61 61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 62 stderr=subprocess.STDOUT)
63 63 p.fromchild = p.stdout
64 64 p.tochild = p.stdin
65 65 p.childerr = p.stderr
66 66 return p
67 67
68 68 # reserved exit code to skip test (used by hghave)
69 69 SKIPPED_STATUS = 80
70 70 SKIPPED_PREFIX = 'skipped: '
71 71 FAILED_PREFIX = 'hghave check failed: '
72 72 PYTHON = sys.executable
73 73 IMPL_PATH = 'PYTHONPATH'
74 74 if 'java' in sys.platform:
75 75 IMPL_PATH = 'JYTHONPATH'
76 76
77 77 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
78 78
79 79 defaults = {
80 80 'jobs': ('HGTEST_JOBS', 1),
81 81 'timeout': ('HGTEST_TIMEOUT', 180),
82 82 'port': ('HGTEST_PORT', 20059),
83 83 }
84 84
85 85 def parseargs():
86 86 parser = optparse.OptionParser("%prog [options] [tests]")
87 87
88 88 # keep these sorted
89 89 parser.add_option("--blacklist", action="append",
90 90 help="skip tests listed in the specified blacklist file")
91 91 parser.add_option("-C", "--annotate", action="store_true",
92 92 help="output files annotated with coverage")
93 93 parser.add_option("--child", type="int",
94 94 help="run as child process, summary to given fd")
95 95 parser.add_option("-c", "--cover", action="store_true",
96 96 help="print a test coverage report")
97 97 parser.add_option("-d", "--debug", action="store_true",
98 98 help="debug mode: write output of test scripts to console"
99 99 " rather than capturing and diff'ing it (disables timeout)")
100 100 parser.add_option("-f", "--first", action="store_true",
101 101 help="exit on the first test failure")
102 102 parser.add_option("--inotify", action="store_true",
103 103 help="enable inotify extension when running tests")
104 104 parser.add_option("-i", "--interactive", action="store_true",
105 105 help="prompt to accept changed output")
106 106 parser.add_option("-j", "--jobs", type="int",
107 107 help="number of jobs to run in parallel"
108 108 " (default: $%s or %d)" % defaults['jobs'])
109 109 parser.add_option("--keep-tmpdir", action="store_true",
110 110 help="keep temporary directory after running tests")
111 111 parser.add_option("-k", "--keywords",
112 112 help="run tests matching keywords")
113 113 parser.add_option("-l", "--local", action="store_true",
114 114 help="shortcut for --with-hg=<testdir>/../hg")
115 115 parser.add_option("-n", "--nodiff", action="store_true",
116 116 help="skip showing test changes")
117 117 parser.add_option("-p", "--port", type="int",
118 118 help="port on which servers should listen"
119 119 " (default: $%s or %d)" % defaults['port'])
120 120 parser.add_option("--pure", action="store_true",
121 121 help="use pure Python code instead of C extensions")
122 122 parser.add_option("-R", "--restart", action="store_true",
123 123 help="restart at last error")
124 124 parser.add_option("-r", "--retest", action="store_true",
125 125 help="retest failed tests")
126 126 parser.add_option("-S", "--noskips", action="store_true",
127 127 help="don't report skip tests verbosely")
128 128 parser.add_option("-t", "--timeout", type="int",
129 129 help="kill errant tests after TIMEOUT seconds"
130 130 " (default: $%s or %d)" % defaults['timeout'])
131 131 parser.add_option("--tmpdir", type="string",
132 132 help="run tests in the given temporary directory"
133 133 " (implies --keep-tmpdir)")
134 134 parser.add_option("-v", "--verbose", action="store_true",
135 135 help="output verbose messages")
136 136 parser.add_option("--view", type="string",
137 137 help="external diff viewer")
138 138 parser.add_option("--with-hg", type="string",
139 139 metavar="HG",
140 140 help="test using specified hg script rather than a "
141 141 "temporary installation")
142 142 parser.add_option("-3", "--py3k-warnings", action="store_true",
143 143 help="enable Py3k warnings on Python 2.6+")
144 144
145 145 for option, default in defaults.items():
146 146 defaults[option] = int(os.environ.get(*default))
147 147 parser.set_defaults(**defaults)
148 148 (options, args) = parser.parse_args()
149 149
150 150 # jython is always pure
151 151 if 'java' in sys.platform or '__pypy__' in sys.modules:
152 152 options.pure = True
153 153
154 154 if options.with_hg:
155 155 if not (os.path.isfile(options.with_hg) and
156 156 os.access(options.with_hg, os.X_OK)):
157 157 parser.error('--with-hg must specify an executable hg script')
158 158 if not os.path.basename(options.with_hg) == 'hg':
159 159 sys.stderr.write('warning: --with-hg should specify an hg script')
160 160 if options.local:
161 161 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
162 162 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
163 163 if not os.access(hgbin, os.X_OK):
164 164 parser.error('--local specified, but %r not found or not executable'
165 165 % hgbin)
166 166 options.with_hg = hgbin
167 167
168 168 options.anycoverage = options.cover or options.annotate
169 169 if options.anycoverage:
170 170 try:
171 171 import coverage
172 172 covver = version.StrictVersion(coverage.__version__).version
173 173 if covver < (3, 3):
174 174 parser.error('coverage options require coverage 3.3 or later')
175 175 except ImportError:
176 176 parser.error('coverage options now require the coverage package')
177 177
178 178 if options.anycoverage and options.local:
179 179 # this needs some path mangling somewhere, I guess
180 180 parser.error("sorry, coverage options do not work when --local "
181 181 "is specified")
182 182
183 183 global vlog
184 184 if options.verbose:
185 185 if options.jobs > 1 or options.child is not None:
186 186 pid = "[%d]" % os.getpid()
187 187 else:
188 188 pid = None
189 189 def vlog(*msg):
190 190 if pid:
191 191 print pid,
192 192 for m in msg:
193 193 print m,
194 194 print
195 195 sys.stdout.flush()
196 196 else:
197 197 vlog = lambda *msg: None
198 198
199 199 if options.tmpdir:
200 200 options.tmpdir = os.path.expanduser(options.tmpdir)
201 201
202 202 if options.jobs < 1:
203 203 parser.error('--jobs must be positive')
204 204 if options.interactive and options.jobs > 1:
205 205 print '(--interactive overrides --jobs)'
206 206 options.jobs = 1
207 207 if options.interactive and options.debug:
208 208 parser.error("-i/--interactive and -d/--debug are incompatible")
209 209 if options.debug:
210 210 if options.timeout != defaults['timeout']:
211 211 sys.stderr.write(
212 212 'warning: --timeout option ignored with --debug\n')
213 213 options.timeout = 0
214 214 if options.py3k_warnings:
215 215 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
216 216 parser.error('--py3k-warnings can only be used on Python 2.6+')
217 217 if options.blacklist:
218 218 blacklist = dict()
219 219 for filename in options.blacklist:
220 220 try:
221 221 path = os.path.expanduser(os.path.expandvars(filename))
222 222 f = open(path, "r")
223 223 except IOError, err:
224 224 if err.errno != errno.ENOENT:
225 225 raise
226 226 print "warning: no such blacklist file: %s" % filename
227 227 continue
228 228
229 229 for line in f.readlines():
230 230 line = line.strip()
231 231 if line and not line.startswith('#'):
232 232 blacklist[line] = filename
233 233
234 234 options.blacklist = blacklist
235 235
236 236 return (options, args)
237 237
238 238 def rename(src, dst):
239 239 """Like os.rename(), trade atomicity and opened files friendliness
240 240 for existing destination support.
241 241 """
242 242 shutil.copy(src, dst)
243 243 os.remove(src)
244 244
245 245 def splitnewlines(text):
246 246 '''like str.splitlines, but only split on newlines.
247 247 keep line endings.'''
248 248 i = 0
249 249 lines = []
250 250 while True:
251 251 n = text.find('\n', i)
252 252 if n == -1:
253 253 last = text[i:]
254 254 if last:
255 255 lines.append(last)
256 256 return lines
257 257 lines.append(text[i:n + 1])
258 258 i = n + 1
259 259
260 260 def parsehghaveoutput(lines):
261 261 '''Parse hghave log lines.
262 262 Return tuple of lists (missing, failed):
263 263 * the missing/unknown features
264 264 * the features for which existence check failed'''
265 265 missing = []
266 266 failed = []
267 267 for line in lines:
268 268 if line.startswith(SKIPPED_PREFIX):
269 269 line = line.splitlines()[0]
270 270 missing.append(line[len(SKIPPED_PREFIX):])
271 271 elif line.startswith(FAILED_PREFIX):
272 272 line = line.splitlines()[0]
273 273 failed.append(line[len(FAILED_PREFIX):])
274 274
275 275 return missing, failed
276 276
277 277 def showdiff(expected, output, ref, err):
278 278 for line in difflib.unified_diff(expected, output, ref, err):
279 279 sys.stdout.write(line)
280 280
281 281 def findprogram(program):
282 282 """Search PATH for a executable program"""
283 283 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
284 284 name = os.path.join(p, program)
285 285 if os.access(name, os.X_OK):
286 286 return name
287 287 return None
288 288
289 289 def checktools():
290 290 # Before we go any further, check for pre-requisite tools
291 291 # stuff from coreutils (cat, rm, etc) are not tested
292 292 for p in requiredtools:
293 293 if os.name == 'nt':
294 294 p += '.exe'
295 295 found = findprogram(p)
296 296 if found:
297 297 vlog("# Found prerequisite", p, "at", found)
298 298 else:
299 299 print "WARNING: Did not find prerequisite tool: "+p
300 300
301 301 def killdaemons():
302 302 # Kill off any leftover daemon processes
303 303 try:
304 304 fp = open(DAEMON_PIDS)
305 305 for line in fp:
306 306 try:
307 307 pid = int(line)
308 308 except ValueError:
309 309 continue
310 310 try:
311 311 os.kill(pid, 0)
312 312 vlog('# Killing daemon process %d' % pid)
313 313 os.kill(pid, signal.SIGTERM)
314 314 time.sleep(0.25)
315 315 os.kill(pid, 0)
316 316 vlog('# Daemon process %d is stuck - really killing it' % pid)
317 317 os.kill(pid, signal.SIGKILL)
318 318 except OSError, err:
319 319 if err.errno != errno.ESRCH:
320 320 raise
321 321 fp.close()
322 322 os.unlink(DAEMON_PIDS)
323 323 except IOError:
324 324 pass
325 325
326 326 def cleanup(options):
327 327 if not options.keep_tmpdir:
328 328 vlog("# Cleaning up HGTMP", HGTMP)
329 329 shutil.rmtree(HGTMP, True)
330 330
331 331 def usecorrectpython():
332 332 # some tests run python interpreter. they must use same
333 333 # interpreter we use or bad things will happen.
334 334 exedir, exename = os.path.split(sys.executable)
335 335 if exename == 'python':
336 336 path = findprogram('python')
337 337 if os.path.dirname(path) == exedir:
338 338 return
339 339 vlog('# Making python executable in test path use correct Python')
340 340 mypython = os.path.join(BINDIR, 'python')
341 341 try:
342 342 os.symlink(sys.executable, mypython)
343 343 except AttributeError:
344 344 # windows fallback
345 345 shutil.copyfile(sys.executable, mypython)
346 346 shutil.copymode(sys.executable, mypython)
347 347
348 348 def installhg(options):
349 349 vlog("# Performing temporary installation of HG")
350 350 installerrs = os.path.join("tests", "install.err")
351 351 pure = options.pure and "--pure" or ""
352 352
353 353 # Run installer in hg root
354 354 script = os.path.realpath(sys.argv[0])
355 355 hgroot = os.path.dirname(os.path.dirname(script))
356 356 os.chdir(hgroot)
357 357 nohome = '--home=""'
358 358 if os.name == 'nt':
359 359 # The --home="" trick works only on OS where os.sep == '/'
360 360 # because of a distutils convert_path() fast-path. Avoid it at
361 361 # least on Windows for now, deal with .pydistutils.cfg bugs
362 362 # when they happen.
363 363 nohome = ''
364 364 cmd = ('%s setup.py %s clean --all'
365 365 ' build --build-base="%s"'
366 366 ' install --force --prefix="%s" --install-lib="%s"'
367 367 ' --install-scripts="%s" %s >%s 2>&1'
368 368 % (sys.executable, pure, os.path.join(HGTMP, "build"),
369 369 INST, PYTHONDIR, BINDIR, nohome, installerrs))
370 370 vlog("# Running", cmd)
371 371 if os.system(cmd) == 0:
372 372 if not options.verbose:
373 373 os.remove(installerrs)
374 374 else:
375 375 f = open(installerrs)
376 376 for line in f:
377 377 print line,
378 378 f.close()
379 379 sys.exit(1)
380 380 os.chdir(TESTDIR)
381 381
382 382 usecorrectpython()
383 383
384 384 vlog("# Installing dummy diffstat")
385 385 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
386 386 f.write('#!' + sys.executable + '\n'
387 387 'import sys\n'
388 388 'files = 0\n'
389 389 'for line in sys.stdin:\n'
390 390 ' if line.startswith("diff "):\n'
391 391 ' files += 1\n'
392 392 'sys.stdout.write("files patched: %d\\n" % files)\n')
393 393 f.close()
394 394 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
395 395
396 396 if options.py3k_warnings and not options.anycoverage:
397 397 vlog("# Updating hg command to enable Py3k Warnings switch")
398 398 f = open(os.path.join(BINDIR, 'hg'), 'r')
399 399 lines = [line.rstrip() for line in f]
400 400 lines[0] += ' -3'
401 401 f.close()
402 402 f = open(os.path.join(BINDIR, 'hg'), 'w')
403 403 for line in lines:
404 404 f.write(line + '\n')
405 405 f.close()
406 406
407 407 if options.anycoverage:
408 408 custom = os.path.join(TESTDIR, 'sitecustomize.py')
409 409 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
410 410 vlog('# Installing coverage trigger to %s' % target)
411 411 shutil.copyfile(custom, target)
412 412 rc = os.path.join(TESTDIR, '.coveragerc')
413 413 vlog('# Installing coverage rc to %s' % rc)
414 414 os.environ['COVERAGE_PROCESS_START'] = rc
415 415 fn = os.path.join(INST, '..', '.coverage')
416 416 os.environ['COVERAGE_FILE'] = fn
417 417
418 418 def outputcoverage(options):
419 419
420 420 vlog('# Producing coverage report')
421 421 os.chdir(PYTHONDIR)
422 422
423 423 def covrun(*args):
424 424 cmd = 'coverage %s' % ' '.join(args)
425 425 vlog('# Running: %s' % cmd)
426 426 os.system(cmd)
427 427
428 428 if options.child:
429 429 return
430 430
431 431 covrun('-c')
432 432 omit = ','.join([BINDIR, TESTDIR])
433 433 covrun('-i', '-r', '"--omit=%s"' % omit) # report
434 434 if options.annotate:
435 435 adir = os.path.join(TESTDIR, 'annotated')
436 436 if not os.path.isdir(adir):
437 437 os.mkdir(adir)
438 438 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
439 439
440 440 class Timeout(Exception):
441 441 pass
442 442
443 443 def alarmed(signum, frame):
444 444 raise Timeout
445 445
446 446 def pytest(test, options, replacements):
447 447 py3kswitch = options.py3k_warnings and ' -3' or ''
448 448 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
449 449 vlog("# Running", cmd)
450 450 return run(cmd, options, replacements)
451 451
452 452 def shtest(test, options, replacements):
453 453 cmd = '"%s"' % test
454 454 vlog("# Running", cmd)
455 455 return run(cmd, options, replacements)
456 456
457 457 def tsttest(test, options, replacements):
458 458 t = open(test)
459 459 out = []
460 460 script = []
461 461 salt = "SALT" + str(time.time())
462 462
463 463 pos = prepos = -1
464 464 after = {}
465 465 expected = {}
466 466 for n, l in enumerate(t):
467 if not l.endswith('\n'):
468 l += '\n'
467 469 if l.startswith(' $ '): # commands
468 470 after.setdefault(pos, []).append(l)
469 471 prepos = pos
470 472 pos = n
471 473 script.append('echo %s %s $?\n' % (salt, n))
472 474 script.append(l[4:])
473 475 elif l.startswith(' > '): # continuations
474 476 after.setdefault(prepos, []).append(l)
475 477 script.append(l[4:])
476 478 elif l.startswith(' '): # results
477 479 # queue up a list of expected results
478 480 expected.setdefault(pos, []).append(l[2:])
479 481 else:
480 482 # non-command/result - queue up for merged output
481 483 after.setdefault(pos, []).append(l)
482 484
483 if script and not script[-1].endswith('\n'):
484 script[-1] = script[-1] + '\n'
485 485 script.append('echo %s %s $?\n' % (salt, n + 1))
486 486
487 487 fd, name = tempfile.mkstemp(suffix='hg-tst')
488 488
489 489 try:
490 490 for l in script:
491 491 os.write(fd, l)
492 492 os.close(fd)
493 493
494 494 cmd = '/bin/sh "%s"' % name
495 495 vlog("# Running", cmd)
496 496 exitcode, output = run(cmd, options, replacements)
497 497 # do not merge output if skipped, return hghave message instead
498 498 if exitcode == SKIPPED_STATUS:
499 499 return exitcode, output
500 500 finally:
501 501 os.remove(name)
502 502
503 503 def rematch(el, l):
504 504 try:
505 505 # ensure that the regex matches to the end of the string
506 506 return re.match(el + r'\Z', l)
507 507 except re.error:
508 508 # el is an invalid regex
509 509 return False
510 510
511 511 def globmatch(el, l):
512 512 # The only supported special characters are * and ?. Escaping is
513 513 # supported.
514 514 i, n = 0, len(el)
515 515 res = ''
516 516 while i < n:
517 517 c = el[i]
518 518 i += 1
519 519 if c == '\\' and el[i] in '*?\\':
520 520 res += el[i - 1:i + 1]
521 521 i += 1
522 522 elif c == '*':
523 523 res += '.*'
524 524 elif c == '?':
525 525 res += '.'
526 526 else:
527 527 res += re.escape(c)
528 528 return rematch(res, l)
529 529
530 530 pos = -1
531 531 postout = []
532 532 ret = 0
533 533 for n, l in enumerate(output):
534 534 if l.startswith(salt):
535 535 # add on last return code
536 536 ret = int(l.split()[2])
537 537 if ret != 0:
538 538 postout.append(" [%s]\n" % ret)
539 539 if pos in after:
540 540 postout += after.pop(pos)
541 541 pos = int(l.split()[1])
542 542 else:
543 543 el = None
544 544 if pos in expected and expected[pos]:
545 545 el = expected[pos].pop(0)
546 546
547 547 if el == l: # perfect match (fast)
548 548 postout.append(" " + l)
549 549 elif el and el.decode('string-escape') == l:
550 550 postout.append(" " + el) # \-escape match
551 551 elif (el and
552 552 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
553 553 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l))):
554 554 postout.append(" " + el) # fallback regex/glob match
555 555 else:
556 556 postout.append(" " + l) # let diff deal with it
557 557
558 558 if pos in after:
559 559 postout += after.pop(pos)
560 560
561 561 return exitcode, postout
562 562
563 563 def run(cmd, options, replacements):
564 564 """Run command in a sub-process, capturing the output (stdout and stderr).
565 565 Return a tuple (exitcode, output). output is None in debug mode."""
566 566 # TODO: Use subprocess.Popen if we're running on Python 2.4
567 567 if options.debug:
568 568 proc = subprocess.Popen(cmd, shell=True)
569 569 ret = proc.wait()
570 570 return (ret, None)
571 571
572 572 if os.name == 'nt' or sys.platform.startswith('java'):
573 573 tochild, fromchild = os.popen4(cmd)
574 574 tochild.close()
575 575 output = fromchild.read()
576 576 ret = fromchild.close()
577 577 if ret == None:
578 578 ret = 0
579 579 else:
580 580 proc = Popen4(cmd)
581 581 def cleanup():
582 582 os.kill(proc.pid, signal.SIGTERM)
583 583 ret = proc.wait()
584 584 if ret == 0:
585 585 ret = signal.SIGTERM << 8
586 586 killdaemons()
587 587 return ret
588 588
589 589 try:
590 590 output = ''
591 591 proc.tochild.close()
592 592 output = proc.fromchild.read()
593 593 ret = proc.wait()
594 594 if os.WIFEXITED(ret):
595 595 ret = os.WEXITSTATUS(ret)
596 596 except Timeout:
597 597 vlog('# Process %d timed out - killing it' % proc.pid)
598 598 ret = cleanup()
599 599 output += ("\n### Abort: timeout after %d seconds.\n"
600 600 % options.timeout)
601 601 except KeyboardInterrupt:
602 602 vlog('# Handling keyboard interrupt')
603 603 cleanup()
604 604 raise
605 605
606 606 for s, r in replacements:
607 607 output = re.sub(s, r, output)
608 608 return ret, splitnewlines(output)
609 609
610 610 def runone(options, test, skips, fails):
611 611 '''tristate output:
612 612 None -> skipped
613 613 True -> passed
614 614 False -> failed'''
615 615
616 616 def skip(msg):
617 617 if not options.verbose:
618 618 skips.append((test, msg))
619 619 else:
620 620 print "\nSkipping %s: %s" % (testpath, msg)
621 621 return None
622 622
623 623 def fail(msg):
624 624 fails.append((test, msg))
625 625 if not options.nodiff:
626 626 print "\nERROR: %s %s" % (testpath, msg)
627 627 return None
628 628
629 629 vlog("# Test", test)
630 630
631 631 # create a fresh hgrc
632 632 hgrc = open(HGRCPATH, 'w+')
633 633 hgrc.write('[ui]\n')
634 634 hgrc.write('slash = True\n')
635 635 hgrc.write('[defaults]\n')
636 636 hgrc.write('backout = -d "0 0"\n')
637 637 hgrc.write('commit = -d "0 0"\n')
638 638 hgrc.write('tag = -d "0 0"\n')
639 639 if options.inotify:
640 640 hgrc.write('[extensions]\n')
641 641 hgrc.write('inotify=\n')
642 642 hgrc.write('[inotify]\n')
643 643 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
644 644 hgrc.write('appendpid=True\n')
645 645 hgrc.close()
646 646
647 647 testpath = os.path.join(TESTDIR, test)
648 648 ref = os.path.join(TESTDIR, test+".out")
649 649 err = os.path.join(TESTDIR, test+".err")
650 650 if os.path.exists(err):
651 651 os.remove(err) # Remove any previous output files
652 652 try:
653 653 tf = open(testpath)
654 654 firstline = tf.readline().rstrip()
655 655 tf.close()
656 656 except:
657 657 firstline = ''
658 658 lctest = test.lower()
659 659
660 660 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
661 661 runner = pytest
662 662 elif lctest.endswith('.t'):
663 663 runner = tsttest
664 664 ref = testpath
665 665 else:
666 666 # do not try to run non-executable programs
667 667 if not os.access(testpath, os.X_OK):
668 668 return skip("not executable")
669 669 runner = shtest
670 670
671 671 # Make a tmp subdirectory to work in
672 672 testtmp = os.environ["TESTTMP"] = os.path.join(HGTMP, test)
673 673 os.mkdir(testtmp)
674 674 os.chdir(testtmp)
675 675
676 676 if options.timeout > 0:
677 677 signal.alarm(options.timeout)
678 678
679 679 ret, out = runner(testpath, options, [
680 680 (re.escape(testtmp), '$TESTTMP'),
681 681 (r':%s\b' % options.port, ':$HGPORT'),
682 682 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
683 683 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
684 684 ])
685 685 vlog("# Ret was:", ret)
686 686
687 687 if options.timeout > 0:
688 688 signal.alarm(0)
689 689
690 690 mark = '.'
691 691
692 692 skipped = (ret == SKIPPED_STATUS)
693 693
694 694 # If we're not in --debug mode and reference output file exists,
695 695 # check test output against it.
696 696 if options.debug:
697 697 refout = None # to match out == None
698 698 elif os.path.exists(ref):
699 699 f = open(ref, "r")
700 700 refout = splitnewlines(f.read())
701 701 f.close()
702 702 else:
703 703 refout = []
704 704
705 705 if (ret != 0 or out != refout) and not skipped and not options.debug:
706 706 # Save errors to a file for diagnosis
707 707 f = open(err, "wb")
708 708 for line in out:
709 709 f.write(line)
710 710 f.close()
711 711
712 712 if skipped:
713 713 mark = 's'
714 714 if out is None: # debug mode: nothing to parse
715 715 missing = ['unknown']
716 716 failed = None
717 717 else:
718 718 missing, failed = parsehghaveoutput(out)
719 719 if not missing:
720 720 missing = ['irrelevant']
721 721 if failed:
722 722 fail("hghave failed checking for %s" % failed[-1])
723 723 skipped = False
724 724 else:
725 725 skip(missing[-1])
726 726 elif out != refout:
727 727 mark = '!'
728 728 if ret:
729 729 fail("output changed and returned error code %d" % ret)
730 730 else:
731 731 fail("output changed")
732 732 if not options.nodiff:
733 733 if options.view:
734 734 os.system("%s %s %s" % (options.view, ref, err))
735 735 else:
736 736 showdiff(refout, out, ref, err)
737 737 ret = 1
738 738 elif ret:
739 739 mark = '!'
740 740 fail("returned error code %d" % ret)
741 741
742 742 if not options.verbose:
743 743 sys.stdout.write(mark)
744 744 sys.stdout.flush()
745 745
746 746 killdaemons()
747 747
748 748 os.chdir(TESTDIR)
749 749 if not options.keep_tmpdir:
750 750 shutil.rmtree(testtmp, True)
751 751 if skipped:
752 752 return None
753 753 return ret == 0
754 754
755 755 _hgpath = None
756 756
757 757 def _gethgpath():
758 758 """Return the path to the mercurial package that is actually found by
759 759 the current Python interpreter."""
760 760 global _hgpath
761 761 if _hgpath is not None:
762 762 return _hgpath
763 763
764 764 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
765 765 pipe = os.popen(cmd % PYTHON)
766 766 try:
767 767 _hgpath = pipe.read().strip()
768 768 finally:
769 769 pipe.close()
770 770 return _hgpath
771 771
772 772 def _checkhglib(verb):
773 773 """Ensure that the 'mercurial' package imported by python is
774 774 the one we expect it to be. If not, print a warning to stderr."""
775 775 expecthg = os.path.join(PYTHONDIR, 'mercurial')
776 776 actualhg = _gethgpath()
777 777 if actualhg != expecthg:
778 778 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
779 779 ' (expected %s)\n'
780 780 % (verb, actualhg, expecthg))
781 781
782 782 def runchildren(options, tests):
783 783 if INST:
784 784 installhg(options)
785 785 _checkhglib("Testing")
786 786
787 787 optcopy = dict(options.__dict__)
788 788 optcopy['jobs'] = 1
789 789 del optcopy['blacklist']
790 790 if optcopy['with_hg'] is None:
791 791 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
792 792 optcopy.pop('anycoverage', None)
793 793
794 794 opts = []
795 795 for opt, value in optcopy.iteritems():
796 796 name = '--' + opt.replace('_', '-')
797 797 if value is True:
798 798 opts.append(name)
799 799 elif value is not None:
800 800 opts.append(name + '=' + str(value))
801 801
802 802 tests.reverse()
803 803 jobs = [[] for j in xrange(options.jobs)]
804 804 while tests:
805 805 for job in jobs:
806 806 if not tests:
807 807 break
808 808 job.append(tests.pop())
809 809 fps = {}
810 810
811 811 for j, job in enumerate(jobs):
812 812 if not job:
813 813 continue
814 814 rfd, wfd = os.pipe()
815 815 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
816 816 childtmp = os.path.join(HGTMP, 'child%d' % j)
817 817 childopts += ['--tmpdir', childtmp]
818 818 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
819 819 vlog(' '.join(cmdline))
820 820 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
821 821 os.close(wfd)
822 822 signal.signal(signal.SIGINT, signal.SIG_IGN)
823 823 failures = 0
824 824 tested, skipped, failed = 0, 0, 0
825 825 skips = []
826 826 fails = []
827 827 while fps:
828 828 pid, status = os.wait()
829 829 fp = fps.pop(pid)
830 830 l = fp.read().splitlines()
831 831 try:
832 832 test, skip, fail = map(int, l[:3])
833 833 except ValueError:
834 834 test, skip, fail = 0, 0, 0
835 835 split = -fail or len(l)
836 836 for s in l[3:split]:
837 837 skips.append(s.split(" ", 1))
838 838 for s in l[split:]:
839 839 fails.append(s.split(" ", 1))
840 840 tested += test
841 841 skipped += skip
842 842 failed += fail
843 843 vlog('pid %d exited, status %d' % (pid, status))
844 844 failures |= status
845 845 print
846 846 if not options.noskips:
847 847 for s in skips:
848 848 print "Skipped %s: %s" % (s[0], s[1])
849 849 for s in fails:
850 850 print "Failed %s: %s" % (s[0], s[1])
851 851
852 852 _checkhglib("Tested")
853 853 print "# Ran %d tests, %d skipped, %d failed." % (
854 854 tested, skipped, failed)
855 855
856 856 if options.anycoverage:
857 857 outputcoverage(options)
858 858 sys.exit(failures != 0)
859 859
860 860 def runtests(options, tests):
861 861 global DAEMON_PIDS, HGRCPATH
862 862 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
863 863 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
864 864
865 865 try:
866 866 if INST:
867 867 installhg(options)
868 868 _checkhglib("Testing")
869 869
870 870 if options.timeout > 0:
871 871 try:
872 872 signal.signal(signal.SIGALRM, alarmed)
873 873 vlog('# Running each test with %d second timeout' %
874 874 options.timeout)
875 875 except AttributeError:
876 876 print 'WARNING: cannot run tests with timeouts'
877 877 options.timeout = 0
878 878
879 879 tested = 0
880 880 failed = 0
881 881 skipped = 0
882 882
883 883 if options.restart:
884 884 orig = list(tests)
885 885 while tests:
886 886 if os.path.exists(tests[0] + ".err"):
887 887 break
888 888 tests.pop(0)
889 889 if not tests:
890 890 print "running all tests"
891 891 tests = orig
892 892
893 893 skips = []
894 894 fails = []
895 895
896 896 for test in tests:
897 897 if options.blacklist:
898 898 filename = options.blacklist.get(test)
899 899 if filename is not None:
900 900 skips.append((test, "blacklisted (%s)" % filename))
901 901 skipped += 1
902 902 continue
903 903
904 904 if options.retest and not os.path.exists(test + ".err"):
905 905 skipped += 1
906 906 continue
907 907
908 908 if options.keywords:
909 909 t = open(test).read().lower() + test.lower()
910 910 for k in options.keywords.lower().split():
911 911 if k in t:
912 912 break
913 913 else:
914 914 skipped += 1
915 915 continue
916 916
917 917 ret = runone(options, test, skips, fails)
918 918 if ret is None:
919 919 skipped += 1
920 920 elif not ret:
921 921 if options.interactive:
922 922 print "Accept this change? [n] ",
923 923 answer = sys.stdin.readline().strip()
924 924 if answer.lower() in "y yes".split():
925 925 if test.endswith(".t"):
926 926 rename(test + ".err", test)
927 927 else:
928 928 rename(test + ".err", test + ".out")
929 929 tested += 1
930 930 fails.pop()
931 931 continue
932 932 failed += 1
933 933 if options.first:
934 934 break
935 935 tested += 1
936 936
937 937 if options.child:
938 938 fp = os.fdopen(options.child, 'w')
939 939 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
940 940 for s in skips:
941 941 fp.write("%s %s\n" % s)
942 942 for s in fails:
943 943 fp.write("%s %s\n" % s)
944 944 fp.close()
945 945 else:
946 946 print
947 947 for s in skips:
948 948 print "Skipped %s: %s" % s
949 949 for s in fails:
950 950 print "Failed %s: %s" % s
951 951 _checkhglib("Tested")
952 952 print "# Ran %d tests, %d skipped, %d failed." % (
953 953 tested, skipped, failed)
954 954
955 955 if options.anycoverage:
956 956 outputcoverage(options)
957 957 except KeyboardInterrupt:
958 958 failed = True
959 959 print "\ninterrupted!"
960 960
961 961 if failed:
962 962 sys.exit(1)
963 963
964 964 def main():
965 965 (options, args) = parseargs()
966 966 if not options.child:
967 967 os.umask(022)
968 968
969 969 checktools()
970 970
971 971 if len(args) == 0:
972 972 args = os.listdir(".")
973 973 args.sort()
974 974
975 975 tests = []
976 976 skipped = []
977 977 for test in args:
978 978 if (test.startswith("test-") and '~' not in test and
979 979 ('.' not in test or test.endswith('.py') or
980 980 test.endswith('.bat') or test.endswith('.t'))):
981 981 if not os.path.exists(test):
982 982 skipped.append(test)
983 983 else:
984 984 tests.append(test)
985 985 if not tests:
986 986 for test in skipped:
987 987 print 'Skipped %s: does not exist' % test
988 988 print "# Ran 0 tests, %d skipped, 0 failed." % len(skipped)
989 989 return
990 990 tests = tests + skipped
991 991
992 992 # Reset some environment variables to well-known values so that
993 993 # the tests produce repeatable output.
994 994 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
995 995 os.environ['TZ'] = 'GMT'
996 996 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
997 997 os.environ['CDPATH'] = ''
998 998 os.environ['COLUMNS'] = '80'
999 999 os.environ['GREP_OPTIONS'] = ''
1000 1000 os.environ['http_proxy'] = ''
1001 1001
1002 1002 # unset env related to hooks
1003 1003 for k in os.environ.keys():
1004 1004 if k.startswith('HG_'):
1005 1005 # can't remove on solaris
1006 1006 os.environ[k] = ''
1007 1007 del os.environ[k]
1008 1008
1009 1009 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1010 1010 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1011 1011 if options.tmpdir:
1012 1012 options.keep_tmpdir = True
1013 1013 tmpdir = options.tmpdir
1014 1014 if os.path.exists(tmpdir):
1015 1015 # Meaning of tmpdir has changed since 1.3: we used to create
1016 1016 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1017 1017 # tmpdir already exists.
1018 1018 sys.exit("error: temp dir %r already exists" % tmpdir)
1019 1019
1020 1020 # Automatically removing tmpdir sounds convenient, but could
1021 1021 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1022 1022 # or "--tmpdir=$HOME".
1023 1023 #vlog("# Removing temp dir", tmpdir)
1024 1024 #shutil.rmtree(tmpdir)
1025 1025 os.makedirs(tmpdir)
1026 1026 else:
1027 1027 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1028 1028 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1029 1029 DAEMON_PIDS = None
1030 1030 HGRCPATH = None
1031 1031
1032 1032 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1033 1033 os.environ["HGMERGE"] = "internal:merge"
1034 1034 os.environ["HGUSER"] = "test"
1035 1035 os.environ["HGENCODING"] = "ascii"
1036 1036 os.environ["HGENCODINGMODE"] = "strict"
1037 1037 os.environ["HGPORT"] = str(options.port)
1038 1038 os.environ["HGPORT1"] = str(options.port + 1)
1039 1039 os.environ["HGPORT2"] = str(options.port + 2)
1040 1040
1041 1041 if options.with_hg:
1042 1042 INST = None
1043 1043 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1044 1044
1045 1045 # This looks redundant with how Python initializes sys.path from
1046 1046 # the location of the script being executed. Needed because the
1047 1047 # "hg" specified by --with-hg is not the only Python script
1048 1048 # executed in the test suite that needs to import 'mercurial'
1049 1049 # ... which means it's not really redundant at all.
1050 1050 PYTHONDIR = BINDIR
1051 1051 else:
1052 1052 INST = os.path.join(HGTMP, "install")
1053 1053 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1054 1054 PYTHONDIR = os.path.join(INST, "lib", "python")
1055 1055
1056 1056 os.environ["BINDIR"] = BINDIR
1057 1057 os.environ["PYTHON"] = PYTHON
1058 1058
1059 1059 if not options.child:
1060 1060 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1061 1061 os.environ["PATH"] = os.pathsep.join(path)
1062 1062
1063 1063 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1064 1064 # can run .../tests/run-tests.py test-foo where test-foo
1065 1065 # adds an extension to HGRC
1066 1066 pypath = [PYTHONDIR, TESTDIR]
1067 1067 # We have to augment PYTHONPATH, rather than simply replacing
1068 1068 # it, in case external libraries are only available via current
1069 1069 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1070 1070 # are in /opt/subversion.)
1071 1071 oldpypath = os.environ.get(IMPL_PATH)
1072 1072 if oldpypath:
1073 1073 pypath.append(oldpypath)
1074 1074 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1075 1075
1076 1076 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1077 1077
1078 1078 vlog("# Using TESTDIR", TESTDIR)
1079 1079 vlog("# Using HGTMP", HGTMP)
1080 1080 vlog("# Using PATH", os.environ["PATH"])
1081 1081 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1082 1082
1083 1083 try:
1084 1084 if len(tests) > 1 and options.jobs > 1:
1085 1085 runchildren(options, tests)
1086 1086 else:
1087 1087 runtests(options, tests)
1088 1088 finally:
1089 1089 time.sleep(1)
1090 1090 cleanup(options)
1091 1091
1092 1092 main()
@@ -1,306 +1,304 b''
1 1 Create configuration
2 2
3 3 $ echo "[ui]" >> $HGRCPATH
4 4 $ echo "interactive=true" >> $HGRCPATH
5 5 $ echo "[extensions]" >> $HGRCPATH
6 6 $ echo "record=" >> $HGRCPATH
7 7
8 8 help (no mq, so no qrecord)
9 9
10 10 $ hg help qrecord
11 11 hg: unknown command 'qrecord'
12 12 Mercurial Distributed SCM
13 13
14 14 basic commands:
15 15
16 16 add add the specified files on the next commit
17 17 annotate show changeset information by line for each file
18 18 clone make a copy of an existing repository
19 19 commit commit the specified files or all outstanding changes
20 20 diff diff repository (or selected files)
21 21 export dump the header and diffs for one or more changesets
22 22 forget forget the specified files on the next commit
23 23 init create a new repository in the given directory
24 24 log show revision history of entire repository or files
25 25 merge merge working directory with another revision
26 26 pull pull changes from the specified source
27 27 push push changes to the specified destination
28 28 remove remove the specified files on the next commit
29 29 serve start stand-alone webserver
30 30 status show changed files in the working directory
31 31 summary summarize working directory state
32 32 update update working directory (or switch revisions)
33 33
34 34 use "hg help" for the full list of commands or "hg -v" for details
35 35 [255]
36 36
37 37 help (mq present)
38 38
39 39 $ echo "mq=" >> $HGRCPATH
40 40 $ hg help qrecord
41 41 hg qrecord [OPTION]... PATCH [FILE]...
42 42
43 43 interactively record a new patch
44 44
45 45 See "hg help qnew" & "hg help record" for more information and usage.
46 46
47 47 options:
48 48
49 49 -e --edit edit commit message
50 50 -g --git use git extended diff format
51 51 -U --currentuser add "From: <current user>" to patch
52 52 -u --user USER add "From: <USER>" to patch
53 53 -D --currentdate add "Date: <current date>" to patch
54 54 -d --date DATE add "Date: <DATE>" to patch
55 55 -I --include PATTERN [+] include names matching the given patterns
56 56 -X --exclude PATTERN [+] exclude names matching the given patterns
57 57 -m --message TEXT use text as commit message
58 58 -l --logfile FILE read commit message from file
59 59
60 60 [+] marked option can be specified multiple times
61 61
62 62 use "hg -v help qrecord" to show global options
63 63
64 64 $ hg init a
65 65 $ cd a
66 66
67 67 Base commit
68 68
69 69 $ cat > 1.txt <<EOF
70 70 > 1
71 71 > 2
72 72 > 3
73 73 > 4
74 74 > 5
75 75 > EOF
76 76 $ cat > 2.txt <<EOF
77 77 > a
78 78 > b
79 79 > c
80 80 > d
81 81 > e
82 82 > f
83 83 > EOF
84 84
85 85 $ mkdir dir
86 86 $ cat > dir/a.txt <<EOF
87 87 > hello world
88 88 >
89 89 > someone
90 90 > up
91 91 > there
92 92 > loves
93 93 > me
94 94 > EOF
95 95
96 96 $ hg add 1.txt 2.txt dir/a.txt
97 97 $ hg commit -m 'initial checkin'
98 98
99 99 Changing files
100 100
101 101 $ sed -e 's/2/2 2/;s/4/4 4/' 1.txt > 1.txt.new
102 102 $ sed -e 's/b/b b/' 2.txt > 2.txt.new
103 103 $ sed -e 's/hello world/hello world!/' dir/a.txt > dir/a.txt.new
104 104
105 105 $ mv -f 1.txt.new 1.txt
106 106 $ mv -f 2.txt.new 2.txt
107 107 $ mv -f dir/a.txt.new dir/a.txt
108 108
109 109 Whole diff
110 110
111 111 $ hg diff --nodates
112 112 diff -r 1057167b20ef 1.txt
113 113 --- a/1.txt
114 114 +++ b/1.txt
115 115 @@ -1,5 +1,5 @@
116 116 1
117 117 -2
118 118 +2 2
119 119 3
120 120 -4
121 121 +4 4
122 122 5
123 123 diff -r 1057167b20ef 2.txt
124 124 --- a/2.txt
125 125 +++ b/2.txt
126 126 @@ -1,5 +1,5 @@
127 127 a
128 128 -b
129 129 +b b
130 130 c
131 131 d
132 132 e
133 133 diff -r 1057167b20ef dir/a.txt
134 134 --- a/dir/a.txt
135 135 +++ b/dir/a.txt
136 136 @@ -1,4 +1,4 @@
137 137 -hello world
138 138 +hello world!
139 139
140 140 someone
141 141 up
142 142
143 143 qrecord a.patch
144 144
145 145 $ hg qrecord -d '0 0' -m aaa a.patch <<EOF
146 146 > y
147 147 > y
148 148 > n
149 149 > y
150 150 > y
151 151 > n
152 152 > EOF
153 153 diff --git a/1.txt b/1.txt
154 154 2 hunks, 2 lines changed
155 155 examine changes to '1.txt'? [Ynsfdaq?]
156 156 @@ -1,3 +1,3 @@
157 157 1
158 158 -2
159 159 +2 2
160 160 3
161 161 record change 1/6 to '1.txt'? [Ynsfdaq?]
162 162 @@ -3,3 +3,3 @@
163 163 3
164 164 -4
165 165 +4 4
166 166 5
167 167 record change 2/6 to '1.txt'? [Ynsfdaq?]
168 168 diff --git a/2.txt b/2.txt
169 169 1 hunks, 1 lines changed
170 170 examine changes to '2.txt'? [Ynsfdaq?]
171 171 @@ -1,5 +1,5 @@
172 172 a
173 173 -b
174 174 +b b
175 175 c
176 176 d
177 177 e
178 178 record change 4/6 to '2.txt'? [Ynsfdaq?]
179 179 diff --git a/dir/a.txt b/dir/a.txt
180 180 1 hunks, 1 lines changed
181 181 examine changes to 'dir/a.txt'? [Ynsfdaq?]
182 182
183 183 After qrecord a.patch 'tip'"
184 184
185 185 $ hg tip -p
186 186 changeset: 1:5d1ca63427ee
187 187 tag: a.patch
188 188 tag: qbase
189 189 tag: qtip
190 190 tag: tip
191 191 user: test
192 192 date: Thu Jan 01 00:00:00 1970 +0000
193 193 summary: aaa
194 194
195 195 diff -r 1057167b20ef -r 5d1ca63427ee 1.txt
196 196 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
197 197 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
198 198 @@ -1,5 +1,5 @@
199 199 1
200 200 -2
201 201 +2 2
202 202 3
203 203 4
204 204 5
205 205 diff -r 1057167b20ef -r 5d1ca63427ee 2.txt
206 206 --- a/2.txt Thu Jan 01 00:00:00 1970 +0000
207 207 +++ b/2.txt Thu Jan 01 00:00:00 1970 +0000
208 208 @@ -1,5 +1,5 @@
209 209 a
210 210 -b
211 211 +b b
212 212 c
213 213 d
214 214 e
215 215
216 216
217 217 After qrecord a.patch 'diff'"
218 218
219 219 $ hg diff --nodates
220 220 diff -r 5d1ca63427ee 1.txt
221 221 --- a/1.txt
222 222 +++ b/1.txt
223 223 @@ -1,5 +1,5 @@
224 224 1
225 225 2 2
226 226 3
227 227 -4
228 228 +4 4
229 229 5
230 230 diff -r 5d1ca63427ee dir/a.txt
231 231 --- a/dir/a.txt
232 232 +++ b/dir/a.txt
233 233 @@ -1,4 +1,4 @@
234 234 -hello world
235 235 +hello world!
236 236
237 237 someone
238 238 up
239 239
240 240 qrecord b.patch
241 241
242 242 $ hg qrecord -d '0 0' -m bbb b.patch <<EOF
243 243 > y
244 244 > y
245 245 > y
246 246 > y
247 247 > EOF
248 248 diff --git a/1.txt b/1.txt
249 249 1 hunks, 1 lines changed
250 250 examine changes to '1.txt'? [Ynsfdaq?]
251 251 @@ -1,5 +1,5 @@
252 252 1
253 253 2 2
254 254 3
255 255 -4
256 256 +4 4
257 257 5
258 258 record change 1/3 to '1.txt'? [Ynsfdaq?]
259 259 diff --git a/dir/a.txt b/dir/a.txt
260 260 1 hunks, 1 lines changed
261 261 examine changes to 'dir/a.txt'? [Ynsfdaq?]
262 262 @@ -1,4 +1,4 @@
263 263 -hello world
264 264 +hello world!
265 265
266 266 someone
267 267 up
268 268 record change 3/3 to 'dir/a.txt'? [Ynsfdaq?]
269 269
270 270 After qrecord b.patch 'tip'
271 271
272 272 $ hg tip -p
273 273 changeset: 2:b056198bf878
274 274 tag: b.patch
275 275 tag: qtip
276 276 tag: tip
277 277 user: test
278 278 date: Thu Jan 01 00:00:00 1970 +0000
279 279 summary: bbb
280 280
281 281 diff -r 5d1ca63427ee -r b056198bf878 1.txt
282 282 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
283 283 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
284 284 @@ -1,5 +1,5 @@
285 285 1
286 286 2 2
287 287 3
288 288 -4
289 289 +4 4
290 290 5
291 291 diff -r 5d1ca63427ee -r b056198bf878 dir/a.txt
292 292 --- a/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
293 293 +++ b/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
294 294 @@ -1,4 +1,4 @@
295 295 -hello world
296 296 +hello world!
297 297
298 298 someone
299 299 up
300 300
301 301
302 302 After qrecord b.patch 'diff'
303 303
304 304 $ hg diff --nodates
305
306 End No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now