##// END OF EJS Templates
Use dummy diffstat in tests and remove older diffstat workaround....
Thomas Arendsen Hein -
r7172:fb1d7a42 default
parent child Browse files
Show More
@@ -1,644 +1,656 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
8 8 # of the GNU General Public License, incorporated herein by reference.
9 9
10 10 import difflib
11 11 import errno
12 12 import optparse
13 13 import os
14 14 import popen2
15 15 import shutil
16 16 import signal
17 17 import sys
18 18 import tempfile
19 19 import time
20 20
21 21 # reserved exit code to skip test (used by hghave)
22 22 SKIPPED_STATUS = 80
23 23 SKIPPED_PREFIX = 'skipped: '
24 24
25 25 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
26 26
27 27 defaults = {
28 28 'jobs': ('HGTEST_JOBS', 1),
29 29 'timeout': ('HGTEST_TIMEOUT', 180),
30 30 'port': ('HGTEST_PORT', 20059),
31 31 }
32 32
33 33 parser = optparse.OptionParser("%prog [options] [tests]")
34 34 parser.add_option("-C", "--annotate", action="store_true",
35 35 help="output files annotated with coverage")
36 36 parser.add_option("--child", type="int",
37 37 help="run as child process, summary to given fd")
38 38 parser.add_option("-c", "--cover", action="store_true",
39 39 help="print a test coverage report")
40 40 parser.add_option("-f", "--first", action="store_true",
41 41 help="exit on the first test failure")
42 42 parser.add_option("-i", "--interactive", action="store_true",
43 43 help="prompt to accept changed output")
44 44 parser.add_option("-j", "--jobs", type="int",
45 45 help="number of jobs to run in parallel"
46 46 " (default: $%s or %d)" % defaults['jobs'])
47 47 parser.add_option("--keep-tmpdir", action="store_true",
48 48 help="keep temporary directory after running tests"
49 49 " (best used with --tmpdir)")
50 50 parser.add_option("-R", "--restart", action="store_true",
51 51 help="restart at last error")
52 52 parser.add_option("-p", "--port", type="int",
53 53 help="port on which servers should listen"
54 54 " (default: $%s or %d)" % defaults['port'])
55 55 parser.add_option("-r", "--retest", action="store_true",
56 56 help="retest failed tests")
57 57 parser.add_option("-s", "--cover_stdlib", action="store_true",
58 58 help="print a test coverage report inc. standard libraries")
59 59 parser.add_option("-t", "--timeout", type="int",
60 60 help="kill errant tests after TIMEOUT seconds"
61 61 " (default: $%s or %d)" % defaults['timeout'])
62 62 parser.add_option("--tmpdir", type="string",
63 63 help="run tests in the given temporary directory")
64 64 parser.add_option("-v", "--verbose", action="store_true",
65 65 help="output verbose messages")
66 66 parser.add_option("--with-hg", type="string",
67 67 help="test existing install at given location")
68 68
69 69 for option, default in defaults.items():
70 70 defaults[option] = int(os.environ.get(*default))
71 71 parser.set_defaults(**defaults)
72 72 (options, args) = parser.parse_args()
73 73 verbose = options.verbose
74 74 coverage = options.cover or options.cover_stdlib or options.annotate
75 75 python = sys.executable
76 76
77 77 if options.jobs < 1:
78 78 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
79 79 sys.exit(1)
80 80 if options.interactive and options.jobs > 1:
81 81 print >> sys.stderr, 'ERROR: cannot mix -interactive and --jobs > 1'
82 82 sys.exit(1)
83 83
84 84 def rename(src, dst):
85 85 """Like os.rename(), trade atomicity and opened files friendliness
86 86 for existing destination support.
87 87 """
88 88 shutil.copy(src, dst)
89 89 os.remove(src)
90 90
91 91 def vlog(*msg):
92 92 if verbose:
93 93 for m in msg:
94 94 print m,
95 95 print
96 96
97 97 def splitnewlines(text):
98 98 '''like str.splitlines, but only split on newlines.
99 99 keep line endings.'''
100 100 i = 0
101 101 lines = []
102 102 while True:
103 103 n = text.find('\n', i)
104 104 if n == -1:
105 105 last = text[i:]
106 106 if last:
107 107 lines.append(last)
108 108 return lines
109 109 lines.append(text[i:n+1])
110 110 i = n + 1
111 111
112 112 def extract_missing_features(lines):
113 113 '''Extract missing/unknown features log lines as a list'''
114 114 missing = []
115 115 for line in lines:
116 116 if not line.startswith(SKIPPED_PREFIX):
117 117 continue
118 118 line = line.splitlines()[0]
119 119 missing.append(line[len(SKIPPED_PREFIX):])
120 120
121 121 return missing
122 122
123 123 def show_diff(expected, output):
124 124 for line in difflib.unified_diff(expected, output,
125 125 "Expected output", "Test output"):
126 126 sys.stdout.write(line)
127 127
128 128 def find_program(program):
129 129 """Search PATH for a executable program"""
130 130 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
131 131 name = os.path.join(p, program)
132 132 if os.access(name, os.X_OK):
133 133 return name
134 134 return None
135 135
136 136 def check_required_tools():
137 137 # Before we go any further, check for pre-requisite tools
138 138 # stuff from coreutils (cat, rm, etc) are not tested
139 139 for p in required_tools:
140 140 if os.name == 'nt':
141 141 p += '.exe'
142 142 found = find_program(p)
143 143 if found:
144 144 vlog("# Found prerequisite", p, "at", found)
145 145 else:
146 146 print "WARNING: Did not find prerequisite tool: "+p
147 147
148 148 def cleanup_exit():
149 149 if not options.keep_tmpdir:
150 150 if verbose:
151 151 print "# Cleaning up HGTMP", HGTMP
152 152 shutil.rmtree(HGTMP, True)
153 153
154 154 def use_correct_python():
155 155 # some tests run python interpreter. they must use same
156 156 # interpreter we use or bad things will happen.
157 157 exedir, exename = os.path.split(sys.executable)
158 158 if exename == 'python':
159 159 path = find_program('python')
160 160 if os.path.dirname(path) == exedir:
161 161 return
162 162 vlog('# Making python executable in test path use correct Python')
163 163 my_python = os.path.join(BINDIR, 'python')
164 164 try:
165 165 os.symlink(sys.executable, my_python)
166 166 except AttributeError:
167 167 # windows fallback
168 168 shutil.copyfile(sys.executable, my_python)
169 169 shutil.copymode(sys.executable, my_python)
170 170
171 171 def install_hg():
172 172 global python
173 173 vlog("# Performing temporary installation of HG")
174 174 installerrs = os.path.join("tests", "install.err")
175 175
176 176 # Run installer in hg root
177 177 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
178 178 cmd = ('%s setup.py clean --all'
179 179 ' install --force --prefix="%s" --install-lib="%s"'
180 180 ' --install-scripts="%s" >%s 2>&1'
181 181 % (sys.executable, INST, PYTHONDIR, BINDIR, installerrs))
182 182 vlog("# Running", cmd)
183 183 if os.system(cmd) == 0:
184 184 if not verbose:
185 185 os.remove(installerrs)
186 186 else:
187 187 f = open(installerrs)
188 188 for line in f:
189 189 print line,
190 190 f.close()
191 191 sys.exit(1)
192 192 os.chdir(TESTDIR)
193 193
194 194 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
195 195
196 196 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
197 197 pythonpath = os.environ.get("PYTHONPATH")
198 198 if pythonpath:
199 199 pythonpath = pydir + os.pathsep + pythonpath
200 200 else:
201 201 pythonpath = pydir
202 202 os.environ["PYTHONPATH"] = pythonpath
203 203
204 204 use_correct_python()
205 205 global hgpkg
206 206 hgpkg = _hgpath()
207 207
208 vlog("# Installing dummy diffstat")
209 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
210 f.write('#!' + sys.executable + '\n'
211 'import sys\n'
212 'files = 0\n'
213 'for line in sys.stdin:\n'
214 ' if line.startswith("diff "):\n'
215 ' files += 1\n'
216 'sys.stdout.write("files patched: %d\\n" % files)\n')
217 f.close()
218 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
219
208 220 if coverage:
209 221 vlog("# Installing coverage wrapper")
210 222 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
211 223 if os.path.exists(COVERAGE_FILE):
212 224 os.unlink(COVERAGE_FILE)
213 225 # Create a wrapper script to invoke hg via coverage.py
214 226 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
215 227 f = open(os.path.join(BINDIR, 'hg'), 'w')
216 228 f.write('#!' + sys.executable + '\n')
217 229 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
218 230 '"%s", "-x", "%s"] + sys.argv[1:])\n' %
219 231 (os.path.join(TESTDIR, 'coverage.py'),
220 232 os.path.join(BINDIR, '_hg.py')))
221 233 f.close()
222 234 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
223 235 python = '"%s" "%s" -x' % (sys.executable,
224 236 os.path.join(TESTDIR,'coverage.py'))
225 237
226 238 def output_coverage():
227 239 vlog("# Producing coverage report")
228 240 omit = [BINDIR, TESTDIR, PYTHONDIR]
229 241 if not options.cover_stdlib:
230 242 # Exclude as system paths (ignoring empty strings seen on win)
231 243 omit += [x for x in sys.path if x != '']
232 244 omit = ','.join(omit)
233 245 os.chdir(PYTHONDIR)
234 246 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
235 247 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
236 248 vlog("# Running: "+cmd)
237 249 os.system(cmd)
238 250 if options.annotate:
239 251 adir = os.path.join(TESTDIR, 'annotated')
240 252 if not os.path.isdir(adir):
241 253 os.mkdir(adir)
242 254 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
243 255 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
244 256 adir, omit)
245 257 vlog("# Running: "+cmd)
246 258 os.system(cmd)
247 259
248 260 class Timeout(Exception):
249 261 pass
250 262
251 263 def alarmed(signum, frame):
252 264 raise Timeout
253 265
254 266 def run(cmd):
255 267 """Run command in a sub-process, capturing the output (stdout and stderr).
256 268 Return the exist code, and output."""
257 269 # TODO: Use subprocess.Popen if we're running on Python 2.4
258 270 if os.name == 'nt':
259 271 tochild, fromchild = os.popen4(cmd)
260 272 tochild.close()
261 273 output = fromchild.read()
262 274 ret = fromchild.close()
263 275 if ret == None:
264 276 ret = 0
265 277 else:
266 278 proc = popen2.Popen4(cmd)
267 279 try:
268 280 output = ''
269 281 proc.tochild.close()
270 282 output = proc.fromchild.read()
271 283 ret = proc.wait()
272 284 if os.WIFEXITED(ret):
273 285 ret = os.WEXITSTATUS(ret)
274 286 except Timeout:
275 287 vlog('# Process %d timed out - killing it' % proc.pid)
276 288 os.kill(proc.pid, signal.SIGTERM)
277 289 ret = proc.wait()
278 290 if ret == 0:
279 291 ret = signal.SIGTERM << 8
280 292 output += ("\n### Abort: timeout after %d seconds.\n"
281 293 % options.timeout)
282 294 return ret, splitnewlines(output)
283 295
284 296 def run_one(test, skips, fails):
285 297 '''tristate output:
286 298 None -> skipped
287 299 True -> passed
288 300 False -> failed'''
289 301
290 302 def skip(msg):
291 303 if not verbose:
292 304 skips.append((test, msg))
293 305 else:
294 306 print "\nSkipping %s: %s" % (test, msg)
295 307 return None
296 308
297 309 def fail(msg):
298 310 fails.append((test, msg))
299 311 print "\nERROR: %s %s" % (test, msg)
300 312 return None
301 313
302 314 vlog("# Test", test)
303 315
304 316 # create a fresh hgrc
305 317 hgrc = file(HGRCPATH, 'w+')
306 318 hgrc.write('[ui]\n')
307 319 hgrc.write('slash = True\n')
308 320 hgrc.write('[defaults]\n')
309 321 hgrc.write('backout = -d "0 0"\n')
310 322 hgrc.write('commit = -d "0 0"\n')
311 323 hgrc.write('debugrawcommit = -d "0 0"\n')
312 324 hgrc.write('tag = -d "0 0"\n')
313 325 hgrc.close()
314 326
315 327 err = os.path.join(TESTDIR, test+".err")
316 328 ref = os.path.join(TESTDIR, test+".out")
317 329 testpath = os.path.join(TESTDIR, test)
318 330
319 331 if os.path.exists(err):
320 332 os.remove(err) # Remove any previous output files
321 333
322 334 # Make a tmp subdirectory to work in
323 335 tmpd = os.path.join(HGTMP, test)
324 336 os.mkdir(tmpd)
325 337 os.chdir(tmpd)
326 338
327 339 try:
328 340 tf = open(testpath)
329 341 firstline = tf.readline().rstrip()
330 342 tf.close()
331 343 except:
332 344 firstline = ''
333 345 lctest = test.lower()
334 346
335 347 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
336 348 cmd = '%s "%s"' % (python, testpath)
337 349 elif lctest.endswith('.bat'):
338 350 # do not run batch scripts on non-windows
339 351 if os.name != 'nt':
340 352 return skip("batch script")
341 353 # To reliably get the error code from batch files on WinXP,
342 354 # the "cmd /c call" prefix is needed. Grrr
343 355 cmd = 'cmd /c call "%s"' % testpath
344 356 else:
345 357 # do not run shell scripts on windows
346 358 if os.name == 'nt':
347 359 return skip("shell script")
348 360 # do not try to run non-executable programs
349 361 if not os.path.exists(testpath):
350 362 return fail("does not exist")
351 363 elif not os.access(testpath, os.X_OK):
352 364 return skip("not executable")
353 365 cmd = '"%s"' % testpath
354 366
355 367 if options.timeout > 0:
356 368 signal.alarm(options.timeout)
357 369
358 370 vlog("# Running", cmd)
359 371 ret, out = run(cmd)
360 372 vlog("# Ret was:", ret)
361 373
362 374 if options.timeout > 0:
363 375 signal.alarm(0)
364 376
365 377 skipped = (ret == SKIPPED_STATUS)
366 378 # If reference output file exists, check test output against it
367 379 if os.path.exists(ref):
368 380 f = open(ref, "r")
369 381 ref_out = splitnewlines(f.read())
370 382 f.close()
371 383 else:
372 384 ref_out = []
373 385 if skipped:
374 386 missing = extract_missing_features(out)
375 387 if not missing:
376 388 missing = ['irrelevant']
377 389 skip(missing[-1])
378 390 elif out != ref_out:
379 391 if ret:
380 392 fail("output changed and returned error code %d" % ret)
381 393 else:
382 394 fail("output changed")
383 395 show_diff(ref_out, out)
384 396 ret = 1
385 397 elif ret:
386 398 fail("returned error code %d" % ret)
387 399
388 400 if not verbose:
389 401 sys.stdout.write(skipped and 's' or '.')
390 402 sys.stdout.flush()
391 403
392 404 if ret != 0 and not skipped:
393 405 # Save errors to a file for diagnosis
394 406 f = open(err, "wb")
395 407 for line in out:
396 408 f.write(line)
397 409 f.close()
398 410
399 411 # Kill off any leftover daemon processes
400 412 try:
401 413 fp = file(DAEMON_PIDS)
402 414 for line in fp:
403 415 try:
404 416 pid = int(line)
405 417 except ValueError:
406 418 continue
407 419 try:
408 420 os.kill(pid, 0)
409 421 vlog('# Killing daemon process %d' % pid)
410 422 os.kill(pid, signal.SIGTERM)
411 423 time.sleep(0.25)
412 424 os.kill(pid, 0)
413 425 vlog('# Daemon process %d is stuck - really killing it' % pid)
414 426 os.kill(pid, signal.SIGKILL)
415 427 except OSError, err:
416 428 if err.errno != errno.ESRCH:
417 429 raise
418 430 fp.close()
419 431 os.unlink(DAEMON_PIDS)
420 432 except IOError:
421 433 pass
422 434
423 435 os.chdir(TESTDIR)
424 436 if not options.keep_tmpdir:
425 437 shutil.rmtree(tmpd, True)
426 438 if skipped:
427 439 return None
428 440 return ret == 0
429 441
430 442 if not options.child:
431 443 os.umask(022)
432 444
433 445 check_required_tools()
434 446
435 447 # Reset some environment variables to well-known values so that
436 448 # the tests produce repeatable output.
437 449 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
438 450 os.environ['TZ'] = 'GMT'
439 451 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
440 452
441 453 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
442 454 HGTMP = os.environ['HGTMP'] = tempfile.mkdtemp('', 'hgtests.', options.tmpdir)
443 455 DAEMON_PIDS = None
444 456 HGRCPATH = None
445 457
446 458 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
447 459 os.environ["HGMERGE"] = "internal:merge"
448 460 os.environ["HGUSER"] = "test"
449 461 os.environ["HGENCODING"] = "ascii"
450 462 os.environ["HGENCODINGMODE"] = "strict"
451 463 os.environ["HGPORT"] = str(options.port)
452 464 os.environ["HGPORT1"] = str(options.port + 1)
453 465 os.environ["HGPORT2"] = str(options.port + 2)
454 466
455 467 if options.with_hg:
456 468 INST = options.with_hg
457 469 else:
458 470 INST = os.path.join(HGTMP, "install")
459 471 BINDIR = os.path.join(INST, "bin")
460 472 PYTHONDIR = os.path.join(INST, "lib", "python")
461 473 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
462 474
463 475 def _hgpath():
464 476 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
465 477 hgpath = os.popen(cmd % python)
466 478 path = hgpath.read().strip()
467 479 hgpath.close()
468 480 return path
469 481
470 482 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
471 483 hgpkg = None
472 484
473 485 def run_children(tests):
474 486 if not options.with_hg:
475 487 install_hg()
476 488 if hgpkg != expecthg:
477 489 print '# Testing unexpected mercurial: %s' % hgpkg
478 490
479 491 optcopy = dict(options.__dict__)
480 492 optcopy['jobs'] = 1
481 493 optcopy['with_hg'] = INST
482 494 opts = []
483 495 for opt, value in optcopy.iteritems():
484 496 name = '--' + opt.replace('_', '-')
485 497 if value is True:
486 498 opts.append(name)
487 499 elif value is not None:
488 500 opts.append(name + '=' + str(value))
489 501
490 502 tests.reverse()
491 503 jobs = [[] for j in xrange(options.jobs)]
492 504 while tests:
493 505 for j in xrange(options.jobs):
494 506 if not tests: break
495 507 jobs[j].append(tests.pop())
496 508 fps = {}
497 509 for j in xrange(len(jobs)):
498 510 job = jobs[j]
499 511 if not job:
500 512 continue
501 513 rfd, wfd = os.pipe()
502 514 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
503 515 cmdline = [python, sys.argv[0]] + opts + childopts + job
504 516 vlog(' '.join(cmdline))
505 517 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
506 518 os.close(wfd)
507 519 failures = 0
508 520 tested, skipped, failed = 0, 0, 0
509 521 skips = []
510 522 fails = []
511 523 while fps:
512 524 pid, status = os.wait()
513 525 fp = fps.pop(pid)
514 526 l = fp.read().splitlines()
515 527 test, skip, fail = map(int, l[:3])
516 528 split = -fail or len(l)
517 529 for s in l[3:split]:
518 530 skips.append(s.split(" ", 1))
519 531 for s in l[split:]:
520 532 fails.append(s.split(" ", 1))
521 533 tested += test
522 534 skipped += skip
523 535 failed += fail
524 536 vlog('pid %d exited, status %d' % (pid, status))
525 537 failures |= status
526 538 print
527 539 for s in skips:
528 540 print "Skipped %s: %s" % (s[0], s[1])
529 541 for s in fails:
530 542 print "Failed %s: %s" % (s[0], s[1])
531 543
532 544 if hgpkg != expecthg:
533 545 print '# Tested unexpected mercurial: %s' % hgpkg
534 546 print "# Ran %d tests, %d skipped, %d failed." % (
535 547 tested, skipped, failed)
536 548 sys.exit(failures != 0)
537 549
538 550 def run_tests(tests):
539 551 global DAEMON_PIDS, HGRCPATH
540 552 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
541 553 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
542 554
543 555 try:
544 556 if not options.with_hg:
545 557 install_hg()
546 558
547 559 if hgpkg != expecthg:
548 560 print '# Testing unexpected mercurial: %s' % hgpkg
549 561
550 562 if options.timeout > 0:
551 563 try:
552 564 signal.signal(signal.SIGALRM, alarmed)
553 565 vlog('# Running tests with %d-second timeout' %
554 566 options.timeout)
555 567 except AttributeError:
556 568 print 'WARNING: cannot run tests with timeouts'
557 569 options.timeout = 0
558 570
559 571 tested = 0
560 572 failed = 0
561 573 skipped = 0
562 574
563 575 if options.restart:
564 576 orig = list(tests)
565 577 while tests:
566 578 if os.path.exists(tests[0] + ".err"):
567 579 break
568 580 tests.pop(0)
569 581 if not tests:
570 582 print "running all tests"
571 583 tests = orig
572 584
573 585 skips = []
574 586 fails = []
575 587 for test in tests:
576 588 if options.retest and not os.path.exists(test + ".err"):
577 589 skipped += 1
578 590 continue
579 591 ret = run_one(test, skips, fails)
580 592 if ret is None:
581 593 skipped += 1
582 594 elif not ret:
583 595 if options.interactive:
584 596 print "Accept this change? [n] ",
585 597 answer = sys.stdin.readline().strip()
586 598 if answer.lower() in "y yes".split():
587 599 rename(test + ".err", test + ".out")
588 600 tested += 1
589 601 fails.pop()
590 602 continue
591 603 failed += 1
592 604 if options.first:
593 605 break
594 606 tested += 1
595 607
596 608 if options.child:
597 609 fp = os.fdopen(options.child, 'w')
598 610 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
599 611 for s in skips:
600 612 fp.write("%s %s\n" % s)
601 613 for s in fails:
602 614 fp.write("%s %s\n" % s)
603 615 fp.close()
604 616 else:
605 617 print
606 618 for s in skips:
607 619 print "Skipped %s: %s" % s
608 620 for s in fails:
609 621 print "Failed %s: %s" % s
610 622 if hgpkg != expecthg:
611 623 print '# Tested unexpected mercurial: %s' % hgpkg
612 624 print "# Ran %d tests, %d skipped, %d failed." % (
613 625 tested, skipped, failed)
614 626
615 627 if coverage:
616 628 output_coverage()
617 629 except KeyboardInterrupt:
618 630 failed = True
619 631 print "\ninterrupted!"
620 632
621 633 if failed:
622 634 sys.exit(1)
623 635
624 636 if len(args) == 0:
625 637 args = os.listdir(".")
626 638 args.sort()
627 639
628 640 tests = []
629 641 for test in args:
630 642 if (test.startswith("test-") and '~' not in test and
631 643 ('.' not in test or test.endswith('.py') or
632 644 test.endswith('.bat'))):
633 645 tests.append(test)
634 646
635 647 vlog("# Using TESTDIR", TESTDIR)
636 648 vlog("# Using HGTMP", HGTMP)
637 649
638 650 try:
639 651 if len(tests) > 1 and options.jobs > 1:
640 652 run_children(tests)
641 653 else:
642 654 run_tests(tests)
643 655 finally:
644 656 cleanup_exit()
@@ -1,87 +1,86 b''
1 1 #!/bin/sh
2 2
3 3 cat <<EOF >> $HGRCPATH
4 4 [extensions]
5 5 notify=
6 6
7 7 [hooks]
8 8 incoming.notify = python:hgext.notify.hook
9 9
10 10 [notify]
11 11 sources = pull
12 12 diffstat = False
13 13
14 14 [usersubs]
15 15 foo@bar = *
16 16
17 17 [reposubs]
18 18 * = baz
19 19 EOF
20 20
21 21 hg help notify
22 22 hg init a
23 23 echo a > a/a
24 24 echo % commit
25 25 hg --traceback --cwd a commit -Ama -d '0 0'
26 26
27 27 echo % clone
28 28 hg --traceback clone a b
29 29
30 30 echo a >> a/a
31 31 echo % commit
32 32 hg --traceback --cwd a commit -Amb -d '1 0'
33 33
34 34 # on Mac OS X 10.5 the tmp path is very long so would get stripped in the subject line
35 35 cat <<EOF >> $HGRCPATH
36 36 [notify]
37 37 maxsubject = 200
38 38 EOF
39 39
40 40 # the python call below wraps continuation lines, which appear on Mac OS X 10.5 because
41 41 # of the very long subject line
42 42 echo '% pull (minimal config)'
43 43 hg --traceback --cwd b pull ../a 2>&1 |
44 44 python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),' |
45 45 sed -e 's/\(Message-Id:\).*/\1/' \
46 46 -e 's/changeset \([0-9a-f]* *\)in .*test-notif/changeset \1in test-notif/' \
47 47 -e 's/^details: .*test-notify/details: test-notify/' \
48 48 -e 's/^Date:.*/Date:/'
49 49
50 50 cat <<EOF >> $HGRCPATH
51 51 [notify]
52 52 config = $HGTMP/.notify.conf
53 53 domain = test.com
54 54 strip = 3
55 55 template = Subject: {desc|firstline|strip}\nFrom: {author}\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
56 56
57 57 [web]
58 58 baseurl = http://test/
59 59 EOF
60 60
61 61 echo % fail for config file is missing
62 62 hg --cwd b rollback
63 63 hg --cwd b pull ../a 2>&1 | grep 'unable to open.*\.notify\.conf' > /dev/null && echo pull failed
64 64
65 65 touch "$HGTMP/.notify.conf"
66 66
67 67 echo % pull
68 68 hg --cwd b rollback
69 69 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
70 70 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/' \
71 71 -e 's/^Date:.*/Date:/'
72 72
73 73 cat << EOF >> $HGRCPATH
74 74 [hooks]
75 75 incoming.notify = python:hgext.notify.hook
76 76
77 77 [notify]
78 78 sources = pull
79 79 diffstat = True
80 80 EOF
81 81
82 82 echo % pull
83 83 hg --cwd b rollback
84 84 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
85 85 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/' \
86 -e 's/^Date:.*/Date:/' \
87 -e 's/^1 files changed/1 file changed/'
86 -e 's/^Date:.*/Date:/'
@@ -1,164 +1,163 b''
1 1 notify extension - hook extension to email notifications on commits/pushes
2 2
3 3 Subscriptions can be managed through hgrc. Default mode is to print
4 4 messages to stdout, for testing and configuring.
5 5
6 6 To use, configure notify extension and enable in hgrc like this:
7 7
8 8 [extensions]
9 9 hgext.notify =
10 10
11 11 [hooks]
12 12 # one email for each incoming changeset
13 13 incoming.notify = python:hgext.notify.hook
14 14 # batch emails when many changesets incoming at one time
15 15 changegroup.notify = python:hgext.notify.hook
16 16
17 17 [notify]
18 18 # config items go in here
19 19
20 20 config items:
21 21
22 22 REQUIRED:
23 23 config = /path/to/file # file containing subscriptions
24 24
25 25 OPTIONAL:
26 26 test = True # print messages to stdout for testing
27 27 strip = 3 # number of slashes to strip for url paths
28 28 domain = example.com # domain to use if committer missing domain
29 29 style = ... # style file to use when formatting email
30 30 template = ... # template to use when formatting email
31 31 incoming = ... # template to use when run as incoming hook
32 32 changegroup = ... # template when run as changegroup hook
33 33 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
34 34 maxsubject = 67 # truncate subject line longer than this
35 35 diffstat = True # add a diffstat before the diff content
36 36 sources = serve # notify if source of incoming changes in this list
37 37 # (serve == ssh or http, push, pull, bundle)
38 38 [email]
39 39 from = user@host.com # email address to send as if none given
40 40 [web]
41 41 baseurl = http://hgserver/... # root of hg web site for browsing commits
42 42
43 43 notify config file has same format as regular hgrc. it has two
44 44 sections so you can express subscriptions in whatever way is handier
45 45 for you.
46 46
47 47 [usersubs]
48 48 # key is subscriber email, value is ","-separated list of glob patterns
49 49 user@host = pattern
50 50
51 51 [reposubs]
52 52 # key is glob pattern, value is ","-separated list of subscriber emails
53 53 pattern = user@host
54 54
55 55 glob patterns are matched against path to repo root.
56 56
57 57 if you like, you can put notify config file in repo that users can
58 58 push changes to, they can manage their own subscriptions.
59 59
60 60 no commands defined
61 61 % commit
62 62 adding a
63 63 % clone
64 64 updating working directory
65 65 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 66 % commit
67 67 % pull (minimal config)
68 68 pulling from ../a
69 69 searching for changes
70 70 adding changesets
71 71 adding manifests
72 72 adding file changes
73 73 added 1 changesets with 1 changes to 1 files
74 74 Content-Type: text/plain; charset="us-ascii"
75 75 MIME-Version: 1.0
76 76 Content-Transfer-Encoding: 7bit
77 77 Date:
78 78 Subject: changeset in test-notify/b: b
79 79 From: test
80 80 X-Hg-Notification: changeset 0647d048b600
81 81 Message-Id:
82 82 To: baz, foo@bar
83 83
84 84 changeset 0647d048b600 in test-notify/b
85 85 details: test-notify/b?cmd=changeset;node=0647d048b600
86 86 description: b
87 87
88 88 diffs (6 lines):
89 89
90 90 diff -r cb9a9f314b8b -r 0647d048b600 a
91 91 --- a/a Thu Jan 01 00:00:00 1970 +0000
92 92 +++ b/a Thu Jan 01 00:00:01 1970 +0000
93 93 @@ -1,1 +1,2 @@
94 94 a
95 95 +a
96 96 (run 'hg update' to get a working copy)
97 97 % fail for config file is missing
98 98 rolling back last transaction
99 99 pull failed
100 100 % pull
101 101 rolling back last transaction
102 102 pulling from ../a
103 103 searching for changes
104 104 adding changesets
105 105 adding manifests
106 106 adding file changes
107 107 added 1 changesets with 1 changes to 1 files
108 108 Content-Type: text/plain; charset="us-ascii"
109 109 MIME-Version: 1.0
110 110 Content-Transfer-Encoding: 7bit
111 111 Date:
112 112 Subject: b
113 113 From: test@test.com
114 114 X-Hg-Notification: changeset 0647d048b600
115 115 Message-Id:
116 116 To: baz@test.com, foo@bar
117 117
118 118 changeset 0647d048b600
119 119 description:
120 120 b
121 121 diffs (6 lines):
122 122
123 123 diff -r cb9a9f314b8b -r 0647d048b600 a
124 124 --- a/a Thu Jan 01 00:00:00 1970 +0000
125 125 +++ b/a Thu Jan 01 00:00:01 1970 +0000
126 126 @@ -1,1 +1,2 @@
127 127 a
128 128 +a
129 129 (run 'hg update' to get a working copy)
130 130 % pull
131 131 rolling back last transaction
132 132 pulling from ../a
133 133 searching for changes
134 134 adding changesets
135 135 adding manifests
136 136 adding file changes
137 137 added 1 changesets with 1 changes to 1 files
138 138 Content-Type: text/plain; charset="us-ascii"
139 139 MIME-Version: 1.0
140 140 Content-Transfer-Encoding: 7bit
141 141 Date:
142 142 Subject: b
143 143 From: test@test.com
144 144 X-Hg-Notification: changeset 0647d048b600
145 145 Message-Id:
146 146 To: baz@test.com, foo@bar
147 147
148 148 changeset 0647d048b600
149 149 description:
150 150 b
151 151 diffstat:
152 152
153 1 file changed, 1 insertion(+)
154 a | 1 +
153 files patched: 1
155 154
156 155 diffs (6 lines):
157 156
158 157 diff -r cb9a9f314b8b -r 0647d048b600 a
159 158 --- a/a Thu Jan 01 00:00:00 1970 +0000
160 159 +++ b/a Thu Jan 01 00:00:01 1970 +0000
161 160 @@ -1,1 +1,2 @@
162 161 a
163 162 +a
164 163 (run 'hg update' to get a working copy)
General Comments 0
You need to be logged in to leave comments. Login now