##// END OF EJS Templates
Add merge to list of required tools.
Lee Cantey -
r2611:1b4eb1f9 default
parent child Browse files
Show More
@@ -1,352 +1,352 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 re
15 import re
16 import shutil
16 import shutil
17 import signal
17 import signal
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20 import time
20 import time
21
21
22 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
22 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed", "merge"]
23
23
24 parser = optparse.OptionParser("%prog [options] [tests]")
24 parser = optparse.OptionParser("%prog [options] [tests]")
25 parser.add_option("-v", "--verbose", action="store_true",
25 parser.add_option("-v", "--verbose", action="store_true",
26 help="output verbose messages")
26 help="output verbose messages")
27 parser.add_option("-t", "--timeout", type="int",
27 parser.add_option("-t", "--timeout", type="int",
28 help="output verbose messages")
28 help="output verbose messages")
29 parser.add_option("-c", "--cover", action="store_true",
29 parser.add_option("-c", "--cover", action="store_true",
30 help="print a test coverage report")
30 help="print a test coverage report")
31 parser.add_option("-s", "--cover_stdlib", action="store_true",
31 parser.add_option("-s", "--cover_stdlib", action="store_true",
32 help="print a test coverage report inc. standard libraries")
32 help="print a test coverage report inc. standard libraries")
33 parser.add_option("-C", "--annotate", action="store_true",
33 parser.add_option("-C", "--annotate", action="store_true",
34 help="output files annotated with coverage")
34 help="output files annotated with coverage")
35 parser.set_defaults(timeout=180)
35 parser.set_defaults(timeout=180)
36 (options, args) = parser.parse_args()
36 (options, args) = parser.parse_args()
37 verbose = options.verbose
37 verbose = options.verbose
38 coverage = options.cover or options.cover_stdlib or options.annotate
38 coverage = options.cover or options.cover_stdlib or options.annotate
39
39
40 def vlog(*msg):
40 def vlog(*msg):
41 if verbose:
41 if verbose:
42 for m in msg:
42 for m in msg:
43 print m,
43 print m,
44 print
44 print
45
45
46 def splitnewlines(text):
46 def splitnewlines(text):
47 '''like str.splitlines, but only split on newlines.
47 '''like str.splitlines, but only split on newlines.
48 keep line endings.'''
48 keep line endings.'''
49 i = 0
49 i = 0
50 lines = []
50 lines = []
51 while True:
51 while True:
52 n = text.find('\n', i)
52 n = text.find('\n', i)
53 if n == -1:
53 if n == -1:
54 last = text[i:]
54 last = text[i:]
55 if last:
55 if last:
56 lines.append(last)
56 lines.append(last)
57 return lines
57 return lines
58 lines.append(text[i:n+1])
58 lines.append(text[i:n+1])
59 i = n + 1
59 i = n + 1
60
60
61 def show_diff(expected, output):
61 def show_diff(expected, output):
62 for line in difflib.unified_diff(expected, output,
62 for line in difflib.unified_diff(expected, output,
63 "Expected output", "Test output"):
63 "Expected output", "Test output"):
64 sys.stdout.write(line)
64 sys.stdout.write(line)
65
65
66 def find_program(program):
66 def find_program(program):
67 """Search PATH for a executable program"""
67 """Search PATH for a executable program"""
68 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
68 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
69 name = os.path.join(p, program)
69 name = os.path.join(p, program)
70 if os.access(name, os.X_OK):
70 if os.access(name, os.X_OK):
71 return name
71 return name
72 return None
72 return None
73
73
74 def check_required_tools():
74 def check_required_tools():
75 # Before we go any further, check for pre-requisite tools
75 # Before we go any further, check for pre-requisite tools
76 # stuff from coreutils (cat, rm, etc) are not tested
76 # stuff from coreutils (cat, rm, etc) are not tested
77 for p in required_tools:
77 for p in required_tools:
78 if os.name == 'nt':
78 if os.name == 'nt':
79 p += '.exe'
79 p += '.exe'
80 found = find_program(p)
80 found = find_program(p)
81 if found:
81 if found:
82 vlog("# Found prerequisite", p, "at", found)
82 vlog("# Found prerequisite", p, "at", found)
83 else:
83 else:
84 print "WARNING: Did not find prerequisite tool: "+p
84 print "WARNING: Did not find prerequisite tool: "+p
85
85
86 def cleanup_exit():
86 def cleanup_exit():
87 if verbose:
87 if verbose:
88 print "# Cleaning up HGTMP", HGTMP
88 print "# Cleaning up HGTMP", HGTMP
89 shutil.rmtree(HGTMP, True)
89 shutil.rmtree(HGTMP, True)
90
90
91 def use_correct_python():
91 def use_correct_python():
92 # some tests run python interpreter. they must use same
92 # some tests run python interpreter. they must use same
93 # interpreter we use or bad things will happen.
93 # interpreter we use or bad things will happen.
94 exedir, exename = os.path.split(sys.executable)
94 exedir, exename = os.path.split(sys.executable)
95 if exename == 'python':
95 if exename == 'python':
96 path = find_program('python')
96 path = find_program('python')
97 if os.path.dirname(path) == exedir:
97 if os.path.dirname(path) == exedir:
98 return
98 return
99 vlog('# Making python executable in test path use correct Python')
99 vlog('# Making python executable in test path use correct Python')
100 my_python = os.path.join(BINDIR, 'python')
100 my_python = os.path.join(BINDIR, 'python')
101 try:
101 try:
102 os.symlink(sys.executable, my_python)
102 os.symlink(sys.executable, my_python)
103 except AttributeError:
103 except AttributeError:
104 # windows fallback
104 # windows fallback
105 shutil.copyfile(sys.executable, my_python)
105 shutil.copyfile(sys.executable, my_python)
106 shutil.copymode(sys.executable, my_python)
106 shutil.copymode(sys.executable, my_python)
107
107
108 def install_hg():
108 def install_hg():
109 vlog("# Performing temporary installation of HG")
109 vlog("# Performing temporary installation of HG")
110 installerrs = os.path.join("tests", "install.err")
110 installerrs = os.path.join("tests", "install.err")
111
111
112 os.chdir("..") # Get back to hg root
112 os.chdir("..") # Get back to hg root
113 cmd = ('%s setup.py clean --all'
113 cmd = ('%s setup.py clean --all'
114 ' install --force --home="%s" --install-lib="%s" >%s 2>&1'
114 ' install --force --home="%s" --install-lib="%s" >%s 2>&1'
115 % (sys.executable, INST, PYTHONDIR, installerrs))
115 % (sys.executable, INST, PYTHONDIR, installerrs))
116 vlog("# Running", cmd)
116 vlog("# Running", cmd)
117 if os.system(cmd) == 0:
117 if os.system(cmd) == 0:
118 if not verbose:
118 if not verbose:
119 os.remove(installerrs)
119 os.remove(installerrs)
120 else:
120 else:
121 f = open(installerrs)
121 f = open(installerrs)
122 for line in f:
122 for line in f:
123 print line,
123 print line,
124 f.close()
124 f.close()
125 sys.exit(1)
125 sys.exit(1)
126 os.chdir(TESTDIR)
126 os.chdir(TESTDIR)
127
127
128 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
128 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
129 os.environ["PYTHONPATH"] = PYTHONDIR
129 os.environ["PYTHONPATH"] = PYTHONDIR
130
130
131 use_correct_python()
131 use_correct_python()
132
132
133 if coverage:
133 if coverage:
134 vlog("# Installing coverage wrapper")
134 vlog("# Installing coverage wrapper")
135 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
135 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
136 if os.path.exists(COVERAGE_FILE):
136 if os.path.exists(COVERAGE_FILE):
137 os.unlink(COVERAGE_FILE)
137 os.unlink(COVERAGE_FILE)
138 # Create a wrapper script to invoke hg via coverage.py
138 # Create a wrapper script to invoke hg via coverage.py
139 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
139 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
140 f = open(os.path.join(BINDIR, 'hg'), 'w')
140 f = open(os.path.join(BINDIR, 'hg'), 'w')
141 f.write('#!' + sys.executable + '\n')
141 f.write('#!' + sys.executable + '\n')
142 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '+ \
142 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '+ \
143 '"%s", "-x", "%s"] + sys.argv[1:])\n' % (
143 '"%s", "-x", "%s"] + sys.argv[1:])\n' % (
144 os.path.join(TESTDIR, 'coverage.py'),
144 os.path.join(TESTDIR, 'coverage.py'),
145 os.path.join(BINDIR, '_hg.py')))
145 os.path.join(BINDIR, '_hg.py')))
146 f.close()
146 f.close()
147 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
147 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
148
148
149 def output_coverage():
149 def output_coverage():
150 vlog("# Producing coverage report")
150 vlog("# Producing coverage report")
151 omit = [BINDIR, TESTDIR, PYTHONDIR]
151 omit = [BINDIR, TESTDIR, PYTHONDIR]
152 if not options.cover_stdlib:
152 if not options.cover_stdlib:
153 # Exclude as system paths (ignoring empty strings seen on win)
153 # Exclude as system paths (ignoring empty strings seen on win)
154 omit += [x for x in sys.path if x != '']
154 omit += [x for x in sys.path if x != '']
155 omit = ','.join(omit)
155 omit = ','.join(omit)
156 os.chdir(PYTHONDIR)
156 os.chdir(PYTHONDIR)
157 cmd = '"%s" "%s" -r "--omit=%s"' % (
157 cmd = '"%s" "%s" -r "--omit=%s"' % (
158 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
158 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
159 vlog("# Running: "+cmd)
159 vlog("# Running: "+cmd)
160 os.system(cmd)
160 os.system(cmd)
161 if options.annotate:
161 if options.annotate:
162 adir = os.path.join(TESTDIR, 'annotated')
162 adir = os.path.join(TESTDIR, 'annotated')
163 if not os.path.isdir(adir):
163 if not os.path.isdir(adir):
164 os.mkdir(adir)
164 os.mkdir(adir)
165 cmd = '"%s" "%s" -a "--directory=%s" "--omit=%s"' % (
165 cmd = '"%s" "%s" -a "--directory=%s" "--omit=%s"' % (
166 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
166 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
167 adir, omit)
167 adir, omit)
168 vlog("# Running: "+cmd)
168 vlog("# Running: "+cmd)
169 os.system(cmd)
169 os.system(cmd)
170
170
171 class Timeout(Exception):
171 class Timeout(Exception):
172 pass
172 pass
173
173
174 def alarmed(signum, frame):
174 def alarmed(signum, frame):
175 raise Timeout
175 raise Timeout
176
176
177 def run(cmd):
177 def run(cmd):
178 """Run command in a sub-process, capturing the output (stdout and stderr).
178 """Run command in a sub-process, capturing the output (stdout and stderr).
179 Return the exist code, and output."""
179 Return the exist code, and output."""
180 # TODO: Use subprocess.Popen if we're running on Python 2.4
180 # TODO: Use subprocess.Popen if we're running on Python 2.4
181 if os.name == 'nt':
181 if os.name == 'nt':
182 tochild, fromchild = os.popen4(cmd)
182 tochild, fromchild = os.popen4(cmd)
183 tochild.close()
183 tochild.close()
184 output = fromchild.read()
184 output = fromchild.read()
185 ret = fromchild.close()
185 ret = fromchild.close()
186 if ret == None:
186 if ret == None:
187 ret = 0
187 ret = 0
188 else:
188 else:
189 proc = popen2.Popen4(cmd)
189 proc = popen2.Popen4(cmd)
190 try:
190 try:
191 output = ''
191 output = ''
192 proc.tochild.close()
192 proc.tochild.close()
193 output = proc.fromchild.read()
193 output = proc.fromchild.read()
194 ret = proc.wait()
194 ret = proc.wait()
195 except Timeout:
195 except Timeout:
196 vlog('# Process %d timed out - killing it' % proc.pid)
196 vlog('# Process %d timed out - killing it' % proc.pid)
197 os.kill(proc.pid, signal.SIGTERM)
197 os.kill(proc.pid, signal.SIGTERM)
198 ret = proc.wait()
198 ret = proc.wait()
199 if ret == 0:
199 if ret == 0:
200 ret = signal.SIGTERM << 8
200 ret = signal.SIGTERM << 8
201 return ret, splitnewlines(output)
201 return ret, splitnewlines(output)
202
202
203 def run_one(test):
203 def run_one(test):
204 vlog("# Test", test)
204 vlog("# Test", test)
205 if not verbose:
205 if not verbose:
206 sys.stdout.write('.')
206 sys.stdout.write('.')
207 sys.stdout.flush()
207 sys.stdout.flush()
208
208
209 err = os.path.join(TESTDIR, test+".err")
209 err = os.path.join(TESTDIR, test+".err")
210 ref = os.path.join(TESTDIR, test+".out")
210 ref = os.path.join(TESTDIR, test+".out")
211
211
212 if os.path.exists(err):
212 if os.path.exists(err):
213 os.remove(err) # Remove any previous output files
213 os.remove(err) # Remove any previous output files
214
214
215 # Make a tmp subdirectory to work in
215 # Make a tmp subdirectory to work in
216 tmpd = os.path.join(HGTMP, test)
216 tmpd = os.path.join(HGTMP, test)
217 os.mkdir(tmpd)
217 os.mkdir(tmpd)
218 os.chdir(tmpd)
218 os.chdir(tmpd)
219
219
220 if test.endswith(".py"):
220 if test.endswith(".py"):
221 cmd = '%s "%s"' % (sys.executable, os.path.join(TESTDIR, test))
221 cmd = '%s "%s"' % (sys.executable, os.path.join(TESTDIR, test))
222 else:
222 else:
223 cmd = '"%s"' % (os.path.join(TESTDIR, test))
223 cmd = '"%s"' % (os.path.join(TESTDIR, test))
224
224
225 # To reliably get the error code from batch files on WinXP,
225 # To reliably get the error code from batch files on WinXP,
226 # the "cmd /c call" prefix is needed. Grrr
226 # the "cmd /c call" prefix is needed. Grrr
227 if os.name == 'nt' and test.endswith(".bat"):
227 if os.name == 'nt' and test.endswith(".bat"):
228 cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test))
228 cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test))
229
229
230 if options.timeout > 0:
230 if options.timeout > 0:
231 signal.alarm(options.timeout)
231 signal.alarm(options.timeout)
232
232
233 vlog("# Running", cmd)
233 vlog("# Running", cmd)
234 ret, out = run(cmd)
234 ret, out = run(cmd)
235 vlog("# Ret was:", ret)
235 vlog("# Ret was:", ret)
236
236
237 if options.timeout > 0:
237 if options.timeout > 0:
238 signal.alarm(0)
238 signal.alarm(0)
239
239
240 diffret = 0
240 diffret = 0
241 # If reference output file exists, check test output against it
241 # If reference output file exists, check test output against it
242 if os.path.exists(ref):
242 if os.path.exists(ref):
243 f = open(ref, "r")
243 f = open(ref, "r")
244 ref_out = splitnewlines(f.read())
244 ref_out = splitnewlines(f.read())
245 f.close()
245 f.close()
246 else:
246 else:
247 ref_out = ['']
247 ref_out = ['']
248 if out != ref_out:
248 if out != ref_out:
249 diffret = 1
249 diffret = 1
250 print "\nERROR: %s output changed" % (test)
250 print "\nERROR: %s output changed" % (test)
251 show_diff(ref_out, out)
251 show_diff(ref_out, out)
252 if ret:
252 if ret:
253 print "\nERROR: %s failed with error code %d" % (test, ret)
253 print "\nERROR: %s failed with error code %d" % (test, ret)
254 elif diffret:
254 elif diffret:
255 ret = diffret
255 ret = diffret
256
256
257 if ret != 0: # Save errors to a file for diagnosis
257 if ret != 0: # Save errors to a file for diagnosis
258 f = open(err, "wb")
258 f = open(err, "wb")
259 for line in out:
259 for line in out:
260 f.write(line)
260 f.write(line)
261 f.close()
261 f.close()
262
262
263 # Kill off any leftover daemon processes
263 # Kill off any leftover daemon processes
264 try:
264 try:
265 fp = file(DAEMON_PIDS)
265 fp = file(DAEMON_PIDS)
266 for line in fp:
266 for line in fp:
267 try:
267 try:
268 pid = int(line)
268 pid = int(line)
269 except ValueError:
269 except ValueError:
270 continue
270 continue
271 try:
271 try:
272 os.kill(pid, 0)
272 os.kill(pid, 0)
273 vlog('# Killing daemon process %d' % pid)
273 vlog('# Killing daemon process %d' % pid)
274 os.kill(pid, signal.SIGTERM)
274 os.kill(pid, signal.SIGTERM)
275 time.sleep(0.25)
275 time.sleep(0.25)
276 os.kill(pid, 0)
276 os.kill(pid, 0)
277 vlog('# Daemon process %d is stuck - really killing it' % pid)
277 vlog('# Daemon process %d is stuck - really killing it' % pid)
278 os.kill(pid, signal.SIGKILL)
278 os.kill(pid, signal.SIGKILL)
279 except OSError, err:
279 except OSError, err:
280 if err.errno != errno.ESRCH:
280 if err.errno != errno.ESRCH:
281 raise
281 raise
282 fp.close()
282 fp.close()
283 os.unlink(DAEMON_PIDS)
283 os.unlink(DAEMON_PIDS)
284 except IOError:
284 except IOError:
285 pass
285 pass
286
286
287 os.chdir(TESTDIR)
287 os.chdir(TESTDIR)
288 shutil.rmtree(tmpd, True)
288 shutil.rmtree(tmpd, True)
289 return ret == 0
289 return ret == 0
290
290
291
291
292 os.umask(022)
292 os.umask(022)
293
293
294 check_required_tools()
294 check_required_tools()
295
295
296 # Reset some environment variables to well-known values so that
296 # Reset some environment variables to well-known values so that
297 # the tests produce repeatable output.
297 # the tests produce repeatable output.
298 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
298 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
299 os.environ['TZ'] = 'GMT'
299 os.environ['TZ'] = 'GMT'
300
300
301 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
301 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
302 os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
302 os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
303 os.environ["HGUSER"] = "test"
303 os.environ["HGUSER"] = "test"
304 os.environ["HGRCPATH"] = ""
304 os.environ["HGRCPATH"] = ""
305
305
306 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
306 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
307 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
307 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
308 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
308 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
309
309
310 vlog("# Using TESTDIR", TESTDIR)
310 vlog("# Using TESTDIR", TESTDIR)
311 vlog("# Using HGTMP", HGTMP)
311 vlog("# Using HGTMP", HGTMP)
312
312
313 INST = os.path.join(HGTMP, "install")
313 INST = os.path.join(HGTMP, "install")
314 BINDIR = os.path.join(INST, "bin")
314 BINDIR = os.path.join(INST, "bin")
315 PYTHONDIR = os.path.join(INST, "lib", "python")
315 PYTHONDIR = os.path.join(INST, "lib", "python")
316 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
316 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
317
317
318 try:
318 try:
319 try:
319 try:
320 install_hg()
320 install_hg()
321
321
322 if options.timeout > 0:
322 if options.timeout > 0:
323 try:
323 try:
324 signal.signal(signal.SIGALRM, alarmed)
324 signal.signal(signal.SIGALRM, alarmed)
325 vlog('# Running tests with %d-second timeout' %
325 vlog('# Running tests with %d-second timeout' %
326 options.timeout)
326 options.timeout)
327 except AttributeError:
327 except AttributeError:
328 print 'WARNING: cannot run tests with timeouts'
328 print 'WARNING: cannot run tests with timeouts'
329 options.timeout = 0
329 options.timeout = 0
330
330
331 tests = 0
331 tests = 0
332 failed = 0
332 failed = 0
333
333
334 if len(args) == 0:
334 if len(args) == 0:
335 args = os.listdir(".")
335 args = os.listdir(".")
336 for test in args:
336 for test in args:
337 if test.startswith("test-") and not '~' in test and not '.' in test:
337 if test.startswith("test-") and not '~' in test and not '.' in test:
338 if not run_one(test):
338 if not run_one(test):
339 failed += 1
339 failed += 1
340 tests += 1
340 tests += 1
341
341
342 print "\n# Ran %d tests, %d failed." % (tests, failed)
342 print "\n# Ran %d tests, %d failed." % (tests, failed)
343 if coverage:
343 if coverage:
344 output_coverage()
344 output_coverage()
345 except KeyboardInterrupt:
345 except KeyboardInterrupt:
346 failed = True
346 failed = True
347 print "\ninterrupted!"
347 print "\ninterrupted!"
348 finally:
348 finally:
349 cleanup_exit()
349 cleanup_exit()
350
350
351 if failed:
351 if failed:
352 sys.exit(1)
352 sys.exit(1)
General Comments 0
You need to be logged in to leave comments. Login now