##// END OF EJS Templates
run-tests: fix _checkhglib() so it's correct when using --with-hg....
Greg Ward -
r8673:a8066f2f default
parent child Browse files
Show More
@@ -1,720 +1,722 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 of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
8 # GNU General Public License version 2, 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 subprocess
14 import subprocess
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 closefds = os.name == 'posix'
21 closefds = os.name == 'posix'
22 def Popen4(cmd, bufsize=-1):
22 def Popen4(cmd, bufsize=-1):
23 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
23 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
24 close_fds=closefds,
24 close_fds=closefds,
25 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
25 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
26 stderr=subprocess.STDOUT)
26 stderr=subprocess.STDOUT)
27 p.fromchild = p.stdout
27 p.fromchild = p.stdout
28 p.tochild = p.stdin
28 p.tochild = p.stdin
29 p.childerr = p.stderr
29 p.childerr = p.stderr
30 return p
30 return p
31
31
32 # reserved exit code to skip test (used by hghave)
32 # reserved exit code to skip test (used by hghave)
33 SKIPPED_STATUS = 80
33 SKIPPED_STATUS = 80
34 SKIPPED_PREFIX = 'skipped: '
34 SKIPPED_PREFIX = 'skipped: '
35 FAILED_PREFIX = 'hghave check failed: '
35 FAILED_PREFIX = 'hghave check failed: '
36 PYTHON = sys.executable
36 PYTHON = sys.executable
37 hgpkg = None
37 hgpkg = None
38
38
39 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
39 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
40
40
41 defaults = {
41 defaults = {
42 'jobs': ('HGTEST_JOBS', 1),
42 'jobs': ('HGTEST_JOBS', 1),
43 'timeout': ('HGTEST_TIMEOUT', 180),
43 'timeout': ('HGTEST_TIMEOUT', 180),
44 'port': ('HGTEST_PORT', 20059),
44 'port': ('HGTEST_PORT', 20059),
45 }
45 }
46
46
47 def parseargs():
47 def parseargs():
48 parser = optparse.OptionParser("%prog [options] [tests]")
48 parser = optparse.OptionParser("%prog [options] [tests]")
49 parser.add_option("-C", "--annotate", action="store_true",
49 parser.add_option("-C", "--annotate", action="store_true",
50 help="output files annotated with coverage")
50 help="output files annotated with coverage")
51 parser.add_option("--child", type="int",
51 parser.add_option("--child", type="int",
52 help="run as child process, summary to given fd")
52 help="run as child process, summary to given fd")
53 parser.add_option("-c", "--cover", action="store_true",
53 parser.add_option("-c", "--cover", action="store_true",
54 help="print a test coverage report")
54 help="print a test coverage report")
55 parser.add_option("-f", "--first", action="store_true",
55 parser.add_option("-f", "--first", action="store_true",
56 help="exit on the first test failure")
56 help="exit on the first test failure")
57 parser.add_option("-i", "--interactive", action="store_true",
57 parser.add_option("-i", "--interactive", action="store_true",
58 help="prompt to accept changed output")
58 help="prompt to accept changed output")
59 parser.add_option("-j", "--jobs", type="int",
59 parser.add_option("-j", "--jobs", type="int",
60 help="number of jobs to run in parallel"
60 help="number of jobs to run in parallel"
61 " (default: $%s or %d)" % defaults['jobs'])
61 " (default: $%s or %d)" % defaults['jobs'])
62 parser.add_option("--keep-tmpdir", action="store_true",
62 parser.add_option("--keep-tmpdir", action="store_true",
63 help="keep temporary directory after running tests"
63 help="keep temporary directory after running tests"
64 " (best used with --tmpdir)")
64 " (best used with --tmpdir)")
65 parser.add_option("-R", "--restart", action="store_true",
65 parser.add_option("-R", "--restart", action="store_true",
66 help="restart at last error")
66 help="restart at last error")
67 parser.add_option("-p", "--port", type="int",
67 parser.add_option("-p", "--port", type="int",
68 help="port on which servers should listen"
68 help="port on which servers should listen"
69 " (default: $%s or %d)" % defaults['port'])
69 " (default: $%s or %d)" % defaults['port'])
70 parser.add_option("-r", "--retest", action="store_true",
70 parser.add_option("-r", "--retest", action="store_true",
71 help="retest failed tests")
71 help="retest failed tests")
72 parser.add_option("-s", "--cover_stdlib", action="store_true",
72 parser.add_option("-s", "--cover_stdlib", action="store_true",
73 help="print a test coverage report inc. standard libraries")
73 help="print a test coverage report inc. standard libraries")
74 parser.add_option("-t", "--timeout", type="int",
74 parser.add_option("-t", "--timeout", type="int",
75 help="kill errant tests after TIMEOUT seconds"
75 help="kill errant tests after TIMEOUT seconds"
76 " (default: $%s or %d)" % defaults['timeout'])
76 " (default: $%s or %d)" % defaults['timeout'])
77 parser.add_option("--tmpdir", type="string",
77 parser.add_option("--tmpdir", type="string",
78 help="run tests in the given temporary directory")
78 help="run tests in the given temporary directory")
79 parser.add_option("-v", "--verbose", action="store_true",
79 parser.add_option("-v", "--verbose", action="store_true",
80 help="output verbose messages")
80 help="output verbose messages")
81 parser.add_option("-n", "--nodiff", action="store_true",
81 parser.add_option("-n", "--nodiff", action="store_true",
82 help="skip showing test changes")
82 help="skip showing test changes")
83 parser.add_option("--with-hg", type="string",
83 parser.add_option("--with-hg", type="string",
84 help="test existing install at given location")
84 help="test existing install at given location")
85 parser.add_option("--pure", action="store_true",
85 parser.add_option("--pure", action="store_true",
86 help="use pure Python code instead of C extensions")
86 help="use pure Python code instead of C extensions")
87
87
88 for option, default in defaults.items():
88 for option, default in defaults.items():
89 defaults[option] = int(os.environ.get(*default))
89 defaults[option] = int(os.environ.get(*default))
90 parser.set_defaults(**defaults)
90 parser.set_defaults(**defaults)
91 (options, args) = parser.parse_args()
91 (options, args) = parser.parse_args()
92
92
93 global vlog
93 global vlog
94 options.anycoverage = (options.cover or
94 options.anycoverage = (options.cover or
95 options.cover_stdlib or
95 options.cover_stdlib or
96 options.annotate)
96 options.annotate)
97
97
98 if options.verbose:
98 if options.verbose:
99 if options.jobs > 1 or options.child is not None:
99 if options.jobs > 1 or options.child is not None:
100 pid = "[%d]" % os.getpid()
100 pid = "[%d]" % os.getpid()
101 else:
101 else:
102 pid = None
102 pid = None
103 def vlog(*msg):
103 def vlog(*msg):
104 if pid:
104 if pid:
105 print pid,
105 print pid,
106 for m in msg:
106 for m in msg:
107 print m,
107 print m,
108 print
108 print
109 else:
109 else:
110 vlog = lambda *msg: None
110 vlog = lambda *msg: None
111
111
112 if options.jobs < 1:
112 if options.jobs < 1:
113 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
113 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
114 sys.exit(1)
114 sys.exit(1)
115 if options.interactive and options.jobs > 1:
115 if options.interactive and options.jobs > 1:
116 print '(--interactive overrides --jobs)'
116 print '(--interactive overrides --jobs)'
117 options.jobs = 1
117 options.jobs = 1
118
118
119 return (options, args)
119 return (options, args)
120
120
121 def rename(src, dst):
121 def rename(src, dst):
122 """Like os.rename(), trade atomicity and opened files friendliness
122 """Like os.rename(), trade atomicity and opened files friendliness
123 for existing destination support.
123 for existing destination support.
124 """
124 """
125 shutil.copy(src, dst)
125 shutil.copy(src, dst)
126 os.remove(src)
126 os.remove(src)
127
127
128 def splitnewlines(text):
128 def splitnewlines(text):
129 '''like str.splitlines, but only split on newlines.
129 '''like str.splitlines, but only split on newlines.
130 keep line endings.'''
130 keep line endings.'''
131 i = 0
131 i = 0
132 lines = []
132 lines = []
133 while True:
133 while True:
134 n = text.find('\n', i)
134 n = text.find('\n', i)
135 if n == -1:
135 if n == -1:
136 last = text[i:]
136 last = text[i:]
137 if last:
137 if last:
138 lines.append(last)
138 lines.append(last)
139 return lines
139 return lines
140 lines.append(text[i:n+1])
140 lines.append(text[i:n+1])
141 i = n + 1
141 i = n + 1
142
142
143 def parsehghaveoutput(lines):
143 def parsehghaveoutput(lines):
144 '''Parse hghave log lines.
144 '''Parse hghave log lines.
145 Return tuple of lists (missing, failed):
145 Return tuple of lists (missing, failed):
146 * the missing/unknown features
146 * the missing/unknown features
147 * the features for which existence check failed'''
147 * the features for which existence check failed'''
148 missing = []
148 missing = []
149 failed = []
149 failed = []
150 for line in lines:
150 for line in lines:
151 if line.startswith(SKIPPED_PREFIX):
151 if line.startswith(SKIPPED_PREFIX):
152 line = line.splitlines()[0]
152 line = line.splitlines()[0]
153 missing.append(line[len(SKIPPED_PREFIX):])
153 missing.append(line[len(SKIPPED_PREFIX):])
154 elif line.startswith(FAILED_PREFIX):
154 elif line.startswith(FAILED_PREFIX):
155 line = line.splitlines()[0]
155 line = line.splitlines()[0]
156 failed.append(line[len(FAILED_PREFIX):])
156 failed.append(line[len(FAILED_PREFIX):])
157
157
158 return missing, failed
158 return missing, failed
159
159
160 def showdiff(expected, output):
160 def showdiff(expected, output):
161 for line in difflib.unified_diff(expected, output,
161 for line in difflib.unified_diff(expected, output,
162 "Expected output", "Test output"):
162 "Expected output", "Test output"):
163 sys.stdout.write(line)
163 sys.stdout.write(line)
164
164
165 def findprogram(program):
165 def findprogram(program):
166 """Search PATH for a executable program"""
166 """Search PATH for a executable program"""
167 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
167 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
168 name = os.path.join(p, program)
168 name = os.path.join(p, program)
169 if os.access(name, os.X_OK):
169 if os.access(name, os.X_OK):
170 return name
170 return name
171 return None
171 return None
172
172
173 def checktools():
173 def checktools():
174 # Before we go any further, check for pre-requisite tools
174 # Before we go any further, check for pre-requisite tools
175 # stuff from coreutils (cat, rm, etc) are not tested
175 # stuff from coreutils (cat, rm, etc) are not tested
176 for p in requiredtools:
176 for p in requiredtools:
177 if os.name == 'nt':
177 if os.name == 'nt':
178 p += '.exe'
178 p += '.exe'
179 found = findprogram(p)
179 found = findprogram(p)
180 if found:
180 if found:
181 vlog("# Found prerequisite", p, "at", found)
181 vlog("# Found prerequisite", p, "at", found)
182 else:
182 else:
183 print "WARNING: Did not find prerequisite tool: "+p
183 print "WARNING: Did not find prerequisite tool: "+p
184
184
185 def cleanup(options):
185 def cleanup(options):
186 if not options.keep_tmpdir:
186 if not options.keep_tmpdir:
187 vlog("# Cleaning up HGTMP", HGTMP)
187 vlog("# Cleaning up HGTMP", HGTMP)
188 shutil.rmtree(HGTMP, True)
188 shutil.rmtree(HGTMP, True)
189
189
190 def usecorrectpython():
190 def usecorrectpython():
191 # some tests run python interpreter. they must use same
191 # some tests run python interpreter. they must use same
192 # interpreter we use or bad things will happen.
192 # interpreter we use or bad things will happen.
193 exedir, exename = os.path.split(sys.executable)
193 exedir, exename = os.path.split(sys.executable)
194 if exename == 'python':
194 if exename == 'python':
195 path = findprogram('python')
195 path = findprogram('python')
196 if os.path.dirname(path) == exedir:
196 if os.path.dirname(path) == exedir:
197 return
197 return
198 vlog('# Making python executable in test path use correct Python')
198 vlog('# Making python executable in test path use correct Python')
199 mypython = os.path.join(BINDIR, 'python')
199 mypython = os.path.join(BINDIR, 'python')
200 try:
200 try:
201 os.symlink(sys.executable, mypython)
201 os.symlink(sys.executable, mypython)
202 except AttributeError:
202 except AttributeError:
203 # windows fallback
203 # windows fallback
204 shutil.copyfile(sys.executable, mypython)
204 shutil.copyfile(sys.executable, mypython)
205 shutil.copymode(sys.executable, mypython)
205 shutil.copymode(sys.executable, mypython)
206
206
207 def installhg(options):
207 def installhg(options):
208 vlog("# Performing temporary installation of HG")
208 vlog("# Performing temporary installation of HG")
209 installerrs = os.path.join("tests", "install.err")
209 installerrs = os.path.join("tests", "install.err")
210 pure = options.pure and "--pure" or ""
210 pure = options.pure and "--pure" or ""
211
211
212 # Run installer in hg root
212 # Run installer in hg root
213 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
213 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
214 cmd = ('%s setup.py %s clean --all'
214 cmd = ('%s setup.py %s clean --all'
215 ' install --force --prefix="%s" --install-lib="%s"'
215 ' install --force --prefix="%s" --install-lib="%s"'
216 ' --install-scripts="%s" >%s 2>&1'
216 ' --install-scripts="%s" >%s 2>&1'
217 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
217 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
218 vlog("# Running", cmd)
218 vlog("# Running", cmd)
219 if os.system(cmd) == 0:
219 if os.system(cmd) == 0:
220 if not options.verbose:
220 if not options.verbose:
221 os.remove(installerrs)
221 os.remove(installerrs)
222 else:
222 else:
223 f = open(installerrs)
223 f = open(installerrs)
224 for line in f:
224 for line in f:
225 print line,
225 print line,
226 f.close()
226 f.close()
227 sys.exit(1)
227 sys.exit(1)
228 os.chdir(TESTDIR)
228 os.chdir(TESTDIR)
229
229
230 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
230 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
231
231
232 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
232 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
233 pythonpath = os.environ.get("PYTHONPATH")
233 pythonpath = os.environ.get("PYTHONPATH")
234 if pythonpath:
234 if pythonpath:
235 pythonpath = pydir + os.pathsep + pythonpath
235 pythonpath = pydir + os.pathsep + pythonpath
236 else:
236 else:
237 pythonpath = pydir
237 pythonpath = pydir
238 os.environ["PYTHONPATH"] = pythonpath
238 os.environ["PYTHONPATH"] = pythonpath
239
239
240 usecorrectpython()
240 usecorrectpython()
241
241
242 vlog("# Installing dummy diffstat")
242 vlog("# Installing dummy diffstat")
243 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
243 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
244 f.write('#!' + sys.executable + '\n'
244 f.write('#!' + sys.executable + '\n'
245 'import sys\n'
245 'import sys\n'
246 'files = 0\n'
246 'files = 0\n'
247 'for line in sys.stdin:\n'
247 'for line in sys.stdin:\n'
248 ' if line.startswith("diff "):\n'
248 ' if line.startswith("diff "):\n'
249 ' files += 1\n'
249 ' files += 1\n'
250 'sys.stdout.write("files patched: %d\\n" % files)\n')
250 'sys.stdout.write("files patched: %d\\n" % files)\n')
251 f.close()
251 f.close()
252 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
252 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
253
253
254 if options.anycoverage:
254 if options.anycoverage:
255 vlog("# Installing coverage wrapper")
255 vlog("# Installing coverage wrapper")
256 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
256 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
257 if os.path.exists(COVERAGE_FILE):
257 if os.path.exists(COVERAGE_FILE):
258 os.unlink(COVERAGE_FILE)
258 os.unlink(COVERAGE_FILE)
259 # Create a wrapper script to invoke hg via coverage.py
259 # Create a wrapper script to invoke hg via coverage.py
260 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
260 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
261 f = open(os.path.join(BINDIR, 'hg'), 'w')
261 f = open(os.path.join(BINDIR, 'hg'), 'w')
262 f.write('#!' + sys.executable + '\n')
262 f.write('#!' + sys.executable + '\n')
263 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
263 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
264 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
264 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
265 (os.path.join(TESTDIR, 'coverage.py'),
265 (os.path.join(TESTDIR, 'coverage.py'),
266 os.path.join(BINDIR, '_hg.py')))
266 os.path.join(BINDIR, '_hg.py')))
267 f.close()
267 f.close()
268 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
268 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
269
269
270 def outputcoverage(options):
270 def outputcoverage(options):
271
271
272 vlog('# Producing coverage report')
272 vlog('# Producing coverage report')
273 os.chdir(PYTHONDIR)
273 os.chdir(PYTHONDIR)
274
274
275 def covrun(*args):
275 def covrun(*args):
276 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
276 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
277 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
277 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
278 vlog('# Running: %s' % cmd)
278 vlog('# Running: %s' % cmd)
279 os.system(cmd)
279 os.system(cmd)
280
280
281 omit = [BINDIR, TESTDIR, PYTHONDIR]
281 omit = [BINDIR, TESTDIR, PYTHONDIR]
282 if not options.cover_stdlib:
282 if not options.cover_stdlib:
283 # Exclude as system paths (ignoring empty strings seen on win)
283 # Exclude as system paths (ignoring empty strings seen on win)
284 omit += [x for x in sys.path if x != '']
284 omit += [x for x in sys.path if x != '']
285 omit = ','.join(omit)
285 omit = ','.join(omit)
286
286
287 covrun('-c') # combine from parallel processes
287 covrun('-c') # combine from parallel processes
288 covrun('-i', '-r', '"--omit=%s"' % omit) # report
288 covrun('-i', '-r', '"--omit=%s"' % omit) # report
289 if options.annotate:
289 if options.annotate:
290 adir = os.path.join(TESTDIR, 'annotated')
290 adir = os.path.join(TESTDIR, 'annotated')
291 if not os.path.isdir(adir):
291 if not os.path.isdir(adir):
292 os.mkdir(adir)
292 os.mkdir(adir)
293 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
293 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
294
294
295 class Timeout(Exception):
295 class Timeout(Exception):
296 pass
296 pass
297
297
298 def alarmed(signum, frame):
298 def alarmed(signum, frame):
299 raise Timeout
299 raise Timeout
300
300
301 def run(cmd, options):
301 def run(cmd, options):
302 """Run command in a sub-process, capturing the output (stdout and stderr).
302 """Run command in a sub-process, capturing the output (stdout and stderr).
303 Return the exist code, and output."""
303 Return the exist code, and output."""
304 # TODO: Use subprocess.Popen if we're running on Python 2.4
304 # TODO: Use subprocess.Popen if we're running on Python 2.4
305 if os.name == 'nt' or sys.platform.startswith('java'):
305 if os.name == 'nt' or sys.platform.startswith('java'):
306 tochild, fromchild = os.popen4(cmd)
306 tochild, fromchild = os.popen4(cmd)
307 tochild.close()
307 tochild.close()
308 output = fromchild.read()
308 output = fromchild.read()
309 ret = fromchild.close()
309 ret = fromchild.close()
310 if ret == None:
310 if ret == None:
311 ret = 0
311 ret = 0
312 else:
312 else:
313 proc = Popen4(cmd)
313 proc = Popen4(cmd)
314 try:
314 try:
315 output = ''
315 output = ''
316 proc.tochild.close()
316 proc.tochild.close()
317 output = proc.fromchild.read()
317 output = proc.fromchild.read()
318 ret = proc.wait()
318 ret = proc.wait()
319 if os.WIFEXITED(ret):
319 if os.WIFEXITED(ret):
320 ret = os.WEXITSTATUS(ret)
320 ret = os.WEXITSTATUS(ret)
321 except Timeout:
321 except Timeout:
322 vlog('# Process %d timed out - killing it' % proc.pid)
322 vlog('# Process %d timed out - killing it' % proc.pid)
323 os.kill(proc.pid, signal.SIGTERM)
323 os.kill(proc.pid, signal.SIGTERM)
324 ret = proc.wait()
324 ret = proc.wait()
325 if ret == 0:
325 if ret == 0:
326 ret = signal.SIGTERM << 8
326 ret = signal.SIGTERM << 8
327 output += ("\n### Abort: timeout after %d seconds.\n"
327 output += ("\n### Abort: timeout after %d seconds.\n"
328 % options.timeout)
328 % options.timeout)
329 return ret, splitnewlines(output)
329 return ret, splitnewlines(output)
330
330
331 def runone(options, test, skips, fails):
331 def runone(options, test, skips, fails):
332 '''tristate output:
332 '''tristate output:
333 None -> skipped
333 None -> skipped
334 True -> passed
334 True -> passed
335 False -> failed'''
335 False -> failed'''
336
336
337 def skip(msg):
337 def skip(msg):
338 if not options.verbose:
338 if not options.verbose:
339 skips.append((test, msg))
339 skips.append((test, msg))
340 else:
340 else:
341 print "\nSkipping %s: %s" % (test, msg)
341 print "\nSkipping %s: %s" % (test, msg)
342 return None
342 return None
343
343
344 def fail(msg):
344 def fail(msg):
345 fails.append((test, msg))
345 fails.append((test, msg))
346 if not options.nodiff:
346 if not options.nodiff:
347 print "\nERROR: %s %s" % (test, msg)
347 print "\nERROR: %s %s" % (test, msg)
348 return None
348 return None
349
349
350 vlog("# Test", test)
350 vlog("# Test", test)
351
351
352 # create a fresh hgrc
352 # create a fresh hgrc
353 hgrc = file(HGRCPATH, 'w+')
353 hgrc = file(HGRCPATH, 'w+')
354 hgrc.write('[ui]\n')
354 hgrc.write('[ui]\n')
355 hgrc.write('slash = True\n')
355 hgrc.write('slash = True\n')
356 hgrc.write('[defaults]\n')
356 hgrc.write('[defaults]\n')
357 hgrc.write('backout = -d "0 0"\n')
357 hgrc.write('backout = -d "0 0"\n')
358 hgrc.write('commit = -d "0 0"\n')
358 hgrc.write('commit = -d "0 0"\n')
359 hgrc.write('tag = -d "0 0"\n')
359 hgrc.write('tag = -d "0 0"\n')
360 hgrc.close()
360 hgrc.close()
361
361
362 err = os.path.join(TESTDIR, test+".err")
362 err = os.path.join(TESTDIR, test+".err")
363 ref = os.path.join(TESTDIR, test+".out")
363 ref = os.path.join(TESTDIR, test+".out")
364 testpath = os.path.join(TESTDIR, test)
364 testpath = os.path.join(TESTDIR, test)
365
365
366 if os.path.exists(err):
366 if os.path.exists(err):
367 os.remove(err) # Remove any previous output files
367 os.remove(err) # Remove any previous output files
368
368
369 # Make a tmp subdirectory to work in
369 # Make a tmp subdirectory to work in
370 tmpd = os.path.join(HGTMP, test)
370 tmpd = os.path.join(HGTMP, test)
371 os.mkdir(tmpd)
371 os.mkdir(tmpd)
372 os.chdir(tmpd)
372 os.chdir(tmpd)
373
373
374 try:
374 try:
375 tf = open(testpath)
375 tf = open(testpath)
376 firstline = tf.readline().rstrip()
376 firstline = tf.readline().rstrip()
377 tf.close()
377 tf.close()
378 except:
378 except:
379 firstline = ''
379 firstline = ''
380 lctest = test.lower()
380 lctest = test.lower()
381
381
382 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
382 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
383 cmd = '%s "%s"' % (PYTHON, testpath)
383 cmd = '%s "%s"' % (PYTHON, testpath)
384 elif lctest.endswith('.bat'):
384 elif lctest.endswith('.bat'):
385 # do not run batch scripts on non-windows
385 # do not run batch scripts on non-windows
386 if os.name != 'nt':
386 if os.name != 'nt':
387 return skip("batch script")
387 return skip("batch script")
388 # To reliably get the error code from batch files on WinXP,
388 # To reliably get the error code from batch files on WinXP,
389 # the "cmd /c call" prefix is needed. Grrr
389 # the "cmd /c call" prefix is needed. Grrr
390 cmd = 'cmd /c call "%s"' % testpath
390 cmd = 'cmd /c call "%s"' % testpath
391 else:
391 else:
392 # do not run shell scripts on windows
392 # do not run shell scripts on windows
393 if os.name == 'nt':
393 if os.name == 'nt':
394 return skip("shell script")
394 return skip("shell script")
395 # do not try to run non-executable programs
395 # do not try to run non-executable programs
396 if not os.path.exists(testpath):
396 if not os.path.exists(testpath):
397 return fail("does not exist")
397 return fail("does not exist")
398 elif not os.access(testpath, os.X_OK):
398 elif not os.access(testpath, os.X_OK):
399 return skip("not executable")
399 return skip("not executable")
400 cmd = '"%s"' % testpath
400 cmd = '"%s"' % testpath
401
401
402 if options.timeout > 0:
402 if options.timeout > 0:
403 signal.alarm(options.timeout)
403 signal.alarm(options.timeout)
404
404
405 vlog("# Running", cmd)
405 vlog("# Running", cmd)
406 ret, out = run(cmd, options)
406 ret, out = run(cmd, options)
407 vlog("# Ret was:", ret)
407 vlog("# Ret was:", ret)
408
408
409 if options.timeout > 0:
409 if options.timeout > 0:
410 signal.alarm(0)
410 signal.alarm(0)
411
411
412 mark = '.'
412 mark = '.'
413
413
414 skipped = (ret == SKIPPED_STATUS)
414 skipped = (ret == SKIPPED_STATUS)
415 # If reference output file exists, check test output against it
415 # If reference output file exists, check test output against it
416 if os.path.exists(ref):
416 if os.path.exists(ref):
417 f = open(ref, "r")
417 f = open(ref, "r")
418 refout = splitnewlines(f.read())
418 refout = splitnewlines(f.read())
419 f.close()
419 f.close()
420 else:
420 else:
421 refout = []
421 refout = []
422 if skipped:
422 if skipped:
423 mark = 's'
423 mark = 's'
424 missing, failed = parsehghaveoutput(out)
424 missing, failed = parsehghaveoutput(out)
425 if not missing:
425 if not missing:
426 missing = ['irrelevant']
426 missing = ['irrelevant']
427 if failed:
427 if failed:
428 fail("hghave failed checking for %s" % failed[-1])
428 fail("hghave failed checking for %s" % failed[-1])
429 skipped = False
429 skipped = False
430 else:
430 else:
431 skip(missing[-1])
431 skip(missing[-1])
432 elif out != refout:
432 elif out != refout:
433 mark = '!'
433 mark = '!'
434 if ret:
434 if ret:
435 fail("output changed and returned error code %d" % ret)
435 fail("output changed and returned error code %d" % ret)
436 else:
436 else:
437 fail("output changed")
437 fail("output changed")
438 if not options.nodiff:
438 if not options.nodiff:
439 showdiff(refout, out)
439 showdiff(refout, out)
440 ret = 1
440 ret = 1
441 elif ret:
441 elif ret:
442 mark = '!'
442 mark = '!'
443 fail("returned error code %d" % ret)
443 fail("returned error code %d" % ret)
444
444
445 if not options.verbose:
445 if not options.verbose:
446 sys.stdout.write(mark)
446 sys.stdout.write(mark)
447 sys.stdout.flush()
447 sys.stdout.flush()
448
448
449 if ret != 0 and not skipped:
449 if ret != 0 and not skipped:
450 # Save errors to a file for diagnosis
450 # Save errors to a file for diagnosis
451 f = open(err, "wb")
451 f = open(err, "wb")
452 for line in out:
452 for line in out:
453 f.write(line)
453 f.write(line)
454 f.close()
454 f.close()
455
455
456 # Kill off any leftover daemon processes
456 # Kill off any leftover daemon processes
457 try:
457 try:
458 fp = file(DAEMON_PIDS)
458 fp = file(DAEMON_PIDS)
459 for line in fp:
459 for line in fp:
460 try:
460 try:
461 pid = int(line)
461 pid = int(line)
462 except ValueError:
462 except ValueError:
463 continue
463 continue
464 try:
464 try:
465 os.kill(pid, 0)
465 os.kill(pid, 0)
466 vlog('# Killing daemon process %d' % pid)
466 vlog('# Killing daemon process %d' % pid)
467 os.kill(pid, signal.SIGTERM)
467 os.kill(pid, signal.SIGTERM)
468 time.sleep(0.25)
468 time.sleep(0.25)
469 os.kill(pid, 0)
469 os.kill(pid, 0)
470 vlog('# Daemon process %d is stuck - really killing it' % pid)
470 vlog('# Daemon process %d is stuck - really killing it' % pid)
471 os.kill(pid, signal.SIGKILL)
471 os.kill(pid, signal.SIGKILL)
472 except OSError, err:
472 except OSError, err:
473 if err.errno != errno.ESRCH:
473 if err.errno != errno.ESRCH:
474 raise
474 raise
475 fp.close()
475 fp.close()
476 os.unlink(DAEMON_PIDS)
476 os.unlink(DAEMON_PIDS)
477 except IOError:
477 except IOError:
478 pass
478 pass
479
479
480 os.chdir(TESTDIR)
480 os.chdir(TESTDIR)
481 if not options.keep_tmpdir:
481 if not options.keep_tmpdir:
482 shutil.rmtree(tmpd, True)
482 shutil.rmtree(tmpd, True)
483 if skipped:
483 if skipped:
484 return None
484 return None
485 return ret == 0
485 return ret == 0
486
486
487 _hgpath = None
487 _hgpath = None
488
488
489 def _gethgpath():
489 def _gethgpath():
490 """Return the path to the mercurial package that is actually found by
490 """Return the path to the mercurial package that is actually found by
491 the current Python interpreter."""
491 the current Python interpreter."""
492 global _hgpath
492 global _hgpath
493 if _hgpath is not None:
493 if _hgpath is not None:
494 return _hgpath
494 return _hgpath
495
495
496 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
496 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
497 pipe = os.popen(cmd % PYTHON)
497 pipe = os.popen(cmd % PYTHON)
498 try:
498 try:
499 _hgpath = pipe.read().strip()
499 _hgpath = pipe.read().strip()
500 finally:
500 finally:
501 pipe.close()
501 pipe.close()
502 return _hgpath
502 return _hgpath
503
503
504 def _checkhglib(verb):
504 def _checkhglib(verb):
505 """Ensure that the 'mercurial' package imported by python is
505 """Ensure that the 'mercurial' package imported by python is
506 the one we expect it to be. If not, print a message to stdout."""
506 the one we expect it to be. If not, print a warning to stderr."""
507 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
507 expecthg = os.path.join(PYTHONDIR, 'mercurial')
508 actualhg = _gethgpath()
508 actualhg = _gethgpath()
509 if actualhg != expecthg:
509 if actualhg != expecthg:
510 print '# %s unexpected mercurial: %s' % (verb, actualhg)
510 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
511 ' (expected %s)\n'
512 % (verb, actualhg, expecthg))
511
513
512 def runchildren(options, tests):
514 def runchildren(options, tests):
513 if not options.with_hg:
515 if not options.with_hg:
514 installhg(options)
516 installhg(options)
515 _checkhglib("Testing")
517 _checkhglib("Testing")
516
518
517 optcopy = dict(options.__dict__)
519 optcopy = dict(options.__dict__)
518 optcopy['jobs'] = 1
520 optcopy['jobs'] = 1
519 optcopy['with_hg'] = INST
521 optcopy['with_hg'] = INST
520 opts = []
522 opts = []
521 for opt, value in optcopy.iteritems():
523 for opt, value in optcopy.iteritems():
522 name = '--' + opt.replace('_', '-')
524 name = '--' + opt.replace('_', '-')
523 if value is True:
525 if value is True:
524 opts.append(name)
526 opts.append(name)
525 elif value is not None:
527 elif value is not None:
526 opts.append(name + '=' + str(value))
528 opts.append(name + '=' + str(value))
527
529
528 tests.reverse()
530 tests.reverse()
529 jobs = [[] for j in xrange(options.jobs)]
531 jobs = [[] for j in xrange(options.jobs)]
530 while tests:
532 while tests:
531 for job in jobs:
533 for job in jobs:
532 if not tests: break
534 if not tests: break
533 job.append(tests.pop())
535 job.append(tests.pop())
534 fps = {}
536 fps = {}
535 for j, job in enumerate(jobs):
537 for j, job in enumerate(jobs):
536 if not job:
538 if not job:
537 continue
539 continue
538 rfd, wfd = os.pipe()
540 rfd, wfd = os.pipe()
539 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
541 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
540 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
542 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
541 vlog(' '.join(cmdline))
543 vlog(' '.join(cmdline))
542 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
544 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
543 os.close(wfd)
545 os.close(wfd)
544 failures = 0
546 failures = 0
545 tested, skipped, failed = 0, 0, 0
547 tested, skipped, failed = 0, 0, 0
546 skips = []
548 skips = []
547 fails = []
549 fails = []
548 while fps:
550 while fps:
549 pid, status = os.wait()
551 pid, status = os.wait()
550 fp = fps.pop(pid)
552 fp = fps.pop(pid)
551 l = fp.read().splitlines()
553 l = fp.read().splitlines()
552 test, skip, fail = map(int, l[:3])
554 test, skip, fail = map(int, l[:3])
553 split = -fail or len(l)
555 split = -fail or len(l)
554 for s in l[3:split]:
556 for s in l[3:split]:
555 skips.append(s.split(" ", 1))
557 skips.append(s.split(" ", 1))
556 for s in l[split:]:
558 for s in l[split:]:
557 fails.append(s.split(" ", 1))
559 fails.append(s.split(" ", 1))
558 tested += test
560 tested += test
559 skipped += skip
561 skipped += skip
560 failed += fail
562 failed += fail
561 vlog('pid %d exited, status %d' % (pid, status))
563 vlog('pid %d exited, status %d' % (pid, status))
562 failures |= status
564 failures |= status
563 print
565 print
564 for s in skips:
566 for s in skips:
565 print "Skipped %s: %s" % (s[0], s[1])
567 print "Skipped %s: %s" % (s[0], s[1])
566 for s in fails:
568 for s in fails:
567 print "Failed %s: %s" % (s[0], s[1])
569 print "Failed %s: %s" % (s[0], s[1])
568
570
569 _checkhglib("Tested")
571 _checkhglib("Tested")
570 print "# Ran %d tests, %d skipped, %d failed." % (
572 print "# Ran %d tests, %d skipped, %d failed." % (
571 tested, skipped, failed)
573 tested, skipped, failed)
572 sys.exit(failures != 0)
574 sys.exit(failures != 0)
573
575
574 def runtests(options, tests):
576 def runtests(options, tests):
575 global DAEMON_PIDS, HGRCPATH
577 global DAEMON_PIDS, HGRCPATH
576 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
578 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
577 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
579 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
578
580
579 try:
581 try:
580 if not options.with_hg:
582 if not options.with_hg:
581 installhg(options)
583 installhg(options)
582 _checkhglib("Testing")
584 _checkhglib("Testing")
583
585
584 if options.timeout > 0:
586 if options.timeout > 0:
585 try:
587 try:
586 signal.signal(signal.SIGALRM, alarmed)
588 signal.signal(signal.SIGALRM, alarmed)
587 vlog('# Running each test with %d second timeout' %
589 vlog('# Running each test with %d second timeout' %
588 options.timeout)
590 options.timeout)
589 except AttributeError:
591 except AttributeError:
590 print 'WARNING: cannot run tests with timeouts'
592 print 'WARNING: cannot run tests with timeouts'
591 options.timeout = 0
593 options.timeout = 0
592
594
593 tested = 0
595 tested = 0
594 failed = 0
596 failed = 0
595 skipped = 0
597 skipped = 0
596
598
597 if options.restart:
599 if options.restart:
598 orig = list(tests)
600 orig = list(tests)
599 while tests:
601 while tests:
600 if os.path.exists(tests[0] + ".err"):
602 if os.path.exists(tests[0] + ".err"):
601 break
603 break
602 tests.pop(0)
604 tests.pop(0)
603 if not tests:
605 if not tests:
604 print "running all tests"
606 print "running all tests"
605 tests = orig
607 tests = orig
606
608
607 skips = []
609 skips = []
608 fails = []
610 fails = []
609 for test in tests:
611 for test in tests:
610 if options.retest and not os.path.exists(test + ".err"):
612 if options.retest and not os.path.exists(test + ".err"):
611 skipped += 1
613 skipped += 1
612 continue
614 continue
613 ret = runone(options, test, skips, fails)
615 ret = runone(options, test, skips, fails)
614 if ret is None:
616 if ret is None:
615 skipped += 1
617 skipped += 1
616 elif not ret:
618 elif not ret:
617 if options.interactive:
619 if options.interactive:
618 print "Accept this change? [n] ",
620 print "Accept this change? [n] ",
619 answer = sys.stdin.readline().strip()
621 answer = sys.stdin.readline().strip()
620 if answer.lower() in "y yes".split():
622 if answer.lower() in "y yes".split():
621 rename(test + ".err", test + ".out")
623 rename(test + ".err", test + ".out")
622 tested += 1
624 tested += 1
623 fails.pop()
625 fails.pop()
624 continue
626 continue
625 failed += 1
627 failed += 1
626 if options.first:
628 if options.first:
627 break
629 break
628 tested += 1
630 tested += 1
629
631
630 if options.child:
632 if options.child:
631 fp = os.fdopen(options.child, 'w')
633 fp = os.fdopen(options.child, 'w')
632 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
634 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
633 for s in skips:
635 for s in skips:
634 fp.write("%s %s\n" % s)
636 fp.write("%s %s\n" % s)
635 for s in fails:
637 for s in fails:
636 fp.write("%s %s\n" % s)
638 fp.write("%s %s\n" % s)
637 fp.close()
639 fp.close()
638 else:
640 else:
639 print
641 print
640 for s in skips:
642 for s in skips:
641 print "Skipped %s: %s" % s
643 print "Skipped %s: %s" % s
642 for s in fails:
644 for s in fails:
643 print "Failed %s: %s" % s
645 print "Failed %s: %s" % s
644 _checkhglib("Tested")
646 _checkhglib("Tested")
645 print "# Ran %d tests, %d skipped, %d failed." % (
647 print "# Ran %d tests, %d skipped, %d failed." % (
646 tested, skipped, failed)
648 tested, skipped, failed)
647
649
648 if options.anycoverage:
650 if options.anycoverage:
649 outputcoverage(options)
651 outputcoverage(options)
650 except KeyboardInterrupt:
652 except KeyboardInterrupt:
651 failed = True
653 failed = True
652 print "\ninterrupted!"
654 print "\ninterrupted!"
653
655
654 if failed:
656 if failed:
655 sys.exit(1)
657 sys.exit(1)
656
658
657 def main():
659 def main():
658 (options, args) = parseargs()
660 (options, args) = parseargs()
659 if not options.child:
661 if not options.child:
660 os.umask(022)
662 os.umask(022)
661
663
662 checktools()
664 checktools()
663
665
664 # Reset some environment variables to well-known values so that
666 # Reset some environment variables to well-known values so that
665 # the tests produce repeatable output.
667 # the tests produce repeatable output.
666 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
668 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
667 os.environ['TZ'] = 'GMT'
669 os.environ['TZ'] = 'GMT'
668 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
670 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
669 os.environ['CDPATH'] = ''
671 os.environ['CDPATH'] = ''
670
672
671 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
673 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
672 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
674 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
673 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
675 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
674 options.tmpdir))
676 options.tmpdir))
675 DAEMON_PIDS = None
677 DAEMON_PIDS = None
676 HGRCPATH = None
678 HGRCPATH = None
677
679
678 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
680 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
679 os.environ["HGMERGE"] = "internal:merge"
681 os.environ["HGMERGE"] = "internal:merge"
680 os.environ["HGUSER"] = "test"
682 os.environ["HGUSER"] = "test"
681 os.environ["HGENCODING"] = "ascii"
683 os.environ["HGENCODING"] = "ascii"
682 os.environ["HGENCODINGMODE"] = "strict"
684 os.environ["HGENCODINGMODE"] = "strict"
683 os.environ["HGPORT"] = str(options.port)
685 os.environ["HGPORT"] = str(options.port)
684 os.environ["HGPORT1"] = str(options.port + 1)
686 os.environ["HGPORT1"] = str(options.port + 1)
685 os.environ["HGPORT2"] = str(options.port + 2)
687 os.environ["HGPORT2"] = str(options.port + 2)
686
688
687 if options.with_hg:
689 if options.with_hg:
688 INST = options.with_hg
690 INST = options.with_hg
689 else:
691 else:
690 INST = os.path.join(HGTMP, "install")
692 INST = os.path.join(HGTMP, "install")
691 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
693 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
692 PYTHONDIR = os.path.join(INST, "lib", "python")
694 PYTHONDIR = os.path.join(INST, "lib", "python")
693 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
695 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
694
696
695 if len(args) == 0:
697 if len(args) == 0:
696 args = os.listdir(".")
698 args = os.listdir(".")
697 args.sort()
699 args.sort()
698
700
699 tests = []
701 tests = []
700 for test in args:
702 for test in args:
701 if (test.startswith("test-") and '~' not in test and
703 if (test.startswith("test-") and '~' not in test and
702 ('.' not in test or test.endswith('.py') or
704 ('.' not in test or test.endswith('.py') or
703 test.endswith('.bat'))):
705 test.endswith('.bat'))):
704 tests.append(test)
706 tests.append(test)
705 if not tests:
707 if not tests:
706 print "# Ran 0 tests, 0 skipped, 0 failed."
708 print "# Ran 0 tests, 0 skipped, 0 failed."
707 return
709 return
708
710
709 vlog("# Using TESTDIR", TESTDIR)
711 vlog("# Using TESTDIR", TESTDIR)
710 vlog("# Using HGTMP", HGTMP)
712 vlog("# Using HGTMP", HGTMP)
711
713
712 try:
714 try:
713 if len(tests) > 1 and options.jobs > 1:
715 if len(tests) > 1 and options.jobs > 1:
714 runchildren(options, tests)
716 runchildren(options, tests)
715 else:
717 else:
716 runtests(options, tests)
718 runtests(options, tests)
717 finally:
719 finally:
718 cleanup(options)
720 cleanup(options)
719
721
720 main()
722 main()
General Comments 0
You need to be logged in to leave comments. Login now