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