##// END OF EJS Templates
Merge with stable
Matt Mackall -
r9937:5c990169 merge default
parent child Browse files
Show More
@@ -1,894 +1,894 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 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 #
38 #
39 # (You could use any subset of the tests: test-s* happens to match
39 # (You could use any subset of the tests: test-s* happens to match
40 # enough that it's worth doing parallel runs, few enough that it
40 # enough that it's worth doing parallel runs, few enough that it
41 # completes fairly quickly, includes both shell and Python scripts, and
41 # completes fairly quickly, includes both shell and Python scripts, and
42 # includes some scripts that run daemon processes.)
42 # includes some scripts that run daemon processes.)
43
43
44 import difflib
44 import difflib
45 import errno
45 import errno
46 import optparse
46 import optparse
47 import os
47 import os
48 import subprocess
48 import subprocess
49 import shutil
49 import shutil
50 import signal
50 import signal
51 import sys
51 import sys
52 import tempfile
52 import tempfile
53 import time
53 import time
54
54
55 closefds = os.name == 'posix'
55 closefds = os.name == 'posix'
56 def Popen4(cmd, bufsize=-1):
56 def Popen4(cmd, bufsize=-1):
57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
58 close_fds=closefds,
58 close_fds=closefds,
59 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
59 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
60 stderr=subprocess.STDOUT)
60 stderr=subprocess.STDOUT)
61 p.fromchild = p.stdout
61 p.fromchild = p.stdout
62 p.tochild = p.stdin
62 p.tochild = p.stdin
63 p.childerr = p.stderr
63 p.childerr = p.stderr
64 return p
64 return p
65
65
66 # reserved exit code to skip test (used by hghave)
66 # reserved exit code to skip test (used by hghave)
67 SKIPPED_STATUS = 80
67 SKIPPED_STATUS = 80
68 SKIPPED_PREFIX = 'skipped: '
68 SKIPPED_PREFIX = 'skipped: '
69 FAILED_PREFIX = 'hghave check failed: '
69 FAILED_PREFIX = 'hghave check failed: '
70 PYTHON = sys.executable
70 PYTHON = sys.executable
71
71
72 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
72 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
73
73
74 defaults = {
74 defaults = {
75 'jobs': ('HGTEST_JOBS', 1),
75 'jobs': ('HGTEST_JOBS', 1),
76 'timeout': ('HGTEST_TIMEOUT', 180),
76 'timeout': ('HGTEST_TIMEOUT', 180),
77 'port': ('HGTEST_PORT', 20059),
77 'port': ('HGTEST_PORT', 20059),
78 }
78 }
79
79
80 def parseargs():
80 def parseargs():
81 parser = optparse.OptionParser("%prog [options] [tests]")
81 parser = optparse.OptionParser("%prog [options] [tests]")
82 parser.add_option("-C", "--annotate", action="store_true",
82 parser.add_option("-C", "--annotate", action="store_true",
83 help="output files annotated with coverage")
83 help="output files annotated with coverage")
84 parser.add_option("--child", type="int",
84 parser.add_option("--child", type="int",
85 help="run as child process, summary to given fd")
85 help="run as child process, summary to given fd")
86 parser.add_option("-c", "--cover", action="store_true",
86 parser.add_option("-c", "--cover", action="store_true",
87 help="print a test coverage report")
87 help="print a test coverage report")
88 parser.add_option("-f", "--first", action="store_true",
88 parser.add_option("-f", "--first", action="store_true",
89 help="exit on the first test failure")
89 help="exit on the first test failure")
90 parser.add_option("-i", "--interactive", action="store_true",
90 parser.add_option("-i", "--interactive", action="store_true",
91 help="prompt to accept changed output")
91 help="prompt to accept changed output")
92 parser.add_option("-j", "--jobs", type="int",
92 parser.add_option("-j", "--jobs", type="int",
93 help="number of jobs to run in parallel"
93 help="number of jobs to run in parallel"
94 " (default: $%s or %d)" % defaults['jobs'])
94 " (default: $%s or %d)" % defaults['jobs'])
95 parser.add_option("-k", "--keywords",
95 parser.add_option("-k", "--keywords",
96 help="run tests matching keywords")
96 help="run tests matching keywords")
97 parser.add_option("--keep-tmpdir", action="store_true",
97 parser.add_option("--keep-tmpdir", action="store_true",
98 help="keep temporary directory after running tests")
98 help="keep temporary directory after running tests")
99 parser.add_option("--tmpdir", type="string",
99 parser.add_option("--tmpdir", type="string",
100 help="run tests in the given temporary directory"
100 help="run tests in the given temporary directory"
101 " (implies --keep-tmpdir)")
101 " (implies --keep-tmpdir)")
102 parser.add_option("-d", "--debug", action="store_true",
102 parser.add_option("-d", "--debug", action="store_true",
103 help="debug mode: write output of test scripts to console"
103 help="debug mode: write output of test scripts to console"
104 " rather than capturing and diff'ing it (disables timeout)")
104 " rather than capturing and diff'ing it (disables timeout)")
105 parser.add_option("-R", "--restart", action="store_true",
105 parser.add_option("-R", "--restart", action="store_true",
106 help="restart at last error")
106 help="restart at last error")
107 parser.add_option("-p", "--port", type="int",
107 parser.add_option("-p", "--port", type="int",
108 help="port on which servers should listen"
108 help="port on which servers should listen"
109 " (default: $%s or %d)" % defaults['port'])
109 " (default: $%s or %d)" % defaults['port'])
110 parser.add_option("-r", "--retest", action="store_true",
110 parser.add_option("-r", "--retest", action="store_true",
111 help="retest failed tests")
111 help="retest failed tests")
112 parser.add_option("-s", "--cover_stdlib", action="store_true",
112 parser.add_option("-s", "--cover_stdlib", action="store_true",
113 help="print a test coverage report inc. standard libraries")
113 help="print a test coverage report inc. standard libraries")
114 parser.add_option("-S", "--noskips", action="store_true",
114 parser.add_option("-S", "--noskips", action="store_true",
115 help="don't report skip tests verbosely")
115 help="don't report skip tests verbosely")
116 parser.add_option("-t", "--timeout", type="int",
116 parser.add_option("-t", "--timeout", type="int",
117 help="kill errant tests after TIMEOUT seconds"
117 help="kill errant tests after TIMEOUT seconds"
118 " (default: $%s or %d)" % defaults['timeout'])
118 " (default: $%s or %d)" % defaults['timeout'])
119 parser.add_option("-v", "--verbose", action="store_true",
119 parser.add_option("-v", "--verbose", action="store_true",
120 help="output verbose messages")
120 help="output verbose messages")
121 parser.add_option("-n", "--nodiff", action="store_true",
121 parser.add_option("-n", "--nodiff", action="store_true",
122 help="skip showing test changes")
122 help="skip showing test changes")
123 parser.add_option("--with-hg", type="string",
123 parser.add_option("--with-hg", type="string",
124 metavar="HG",
124 metavar="HG",
125 help="test using specified hg script rather than a "
125 help="test using specified hg script rather than a "
126 "temporary installation")
126 "temporary installation")
127 parser.add_option("--local", action="store_true",
127 parser.add_option("--local", action="store_true",
128 help="shortcut for --with-hg=<testdir>/../hg")
128 help="shortcut for --with-hg=<testdir>/../hg")
129 parser.add_option("--pure", action="store_true",
129 parser.add_option("--pure", action="store_true",
130 help="use pure Python code instead of C extensions")
130 help="use pure Python code instead of C extensions")
131 parser.add_option("-3", "--py3k-warnings", action="store_true",
131 parser.add_option("-3", "--py3k-warnings", action="store_true",
132 help="enable Py3k warnings on Python 2.6+")
132 help="enable Py3k warnings on Python 2.6+")
133
133
134 for option, default in defaults.items():
134 for option, default in defaults.items():
135 defaults[option] = int(os.environ.get(*default))
135 defaults[option] = int(os.environ.get(*default))
136 parser.set_defaults(**defaults)
136 parser.set_defaults(**defaults)
137 (options, args) = parser.parse_args()
137 (options, args) = parser.parse_args()
138
138
139 if options.with_hg:
139 if options.with_hg:
140 if not (os.path.isfile(options.with_hg) and
140 if not (os.path.isfile(options.with_hg) and
141 os.access(options.with_hg, os.X_OK)):
141 os.access(options.with_hg, os.X_OK)):
142 parser.error('--with-hg must specify an executable hg script')
142 parser.error('--with-hg must specify an executable hg script')
143 if not os.path.basename(options.with_hg) == 'hg':
143 if not os.path.basename(options.with_hg) == 'hg':
144 sys.stderr.write('warning: --with-hg should specify an hg script')
144 sys.stderr.write('warning: --with-hg should specify an hg script')
145 if options.local:
145 if options.local:
146 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
146 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
147 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
147 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
148 if not os.access(hgbin, os.X_OK):
148 if not os.access(hgbin, os.X_OK):
149 parser.error('--local specified, but %r not found or not executable'
149 parser.error('--local specified, but %r not found or not executable'
150 % hgbin)
150 % hgbin)
151 options.with_hg = hgbin
151 options.with_hg = hgbin
152
152
153 options.anycoverage = (options.cover or
153 options.anycoverage = (options.cover or
154 options.cover_stdlib or
154 options.cover_stdlib or
155 options.annotate)
155 options.annotate)
156
156
157 if options.anycoverage and options.with_hg:
157 if options.anycoverage and options.with_hg:
158 # I'm not sure if this is a fundamental limitation or just a
158 # I'm not sure if this is a fundamental limitation or just a
159 # bug. But I don't want to waste people's time and energy doing
159 # bug. But I don't want to waste people's time and energy doing
160 # test runs that don't give the results they want.
160 # test runs that don't give the results they want.
161 parser.error("sorry, coverage options do not work when --with-hg "
161 parser.error("sorry, coverage options do not work when --with-hg "
162 "or --local specified")
162 "or --local specified")
163
163
164 global vlog
164 global vlog
165 if options.verbose:
165 if options.verbose:
166 if options.jobs > 1 or options.child is not None:
166 if options.jobs > 1 or options.child is not None:
167 pid = "[%d]" % os.getpid()
167 pid = "[%d]" % os.getpid()
168 else:
168 else:
169 pid = None
169 pid = None
170 def vlog(*msg):
170 def vlog(*msg):
171 if pid:
171 if pid:
172 print pid,
172 print pid,
173 for m in msg:
173 for m in msg:
174 print m,
174 print m,
175 print
175 print
176 sys.stdout.flush()
176 sys.stdout.flush()
177 else:
177 else:
178 vlog = lambda *msg: None
178 vlog = lambda *msg: None
179
179
180 if options.tmpdir:
180 if options.tmpdir:
181 options.tmpdir = os.path.expanduser(options.tmpdir)
181 options.tmpdir = os.path.expanduser(options.tmpdir)
182
182
183 if options.jobs < 1:
183 if options.jobs < 1:
184 parser.error('--jobs must be positive')
184 parser.error('--jobs must be positive')
185 if options.interactive and options.jobs > 1:
185 if options.interactive and options.jobs > 1:
186 print '(--interactive overrides --jobs)'
186 print '(--interactive overrides --jobs)'
187 options.jobs = 1
187 options.jobs = 1
188 if options.interactive and options.debug:
188 if options.interactive and options.debug:
189 parser.error("-i/--interactive and -d/--debug are incompatible")
189 parser.error("-i/--interactive and -d/--debug are incompatible")
190 if options.debug:
190 if options.debug:
191 if options.timeout != defaults['timeout']:
191 if options.timeout != defaults['timeout']:
192 sys.stderr.write(
192 sys.stderr.write(
193 'warning: --timeout option ignored with --debug\n')
193 'warning: --timeout option ignored with --debug\n')
194 options.timeout = 0
194 options.timeout = 0
195 if options.py3k_warnings:
195 if options.py3k_warnings:
196 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
196 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
197 parser.error('--py3k-warnings can only be used on Python 2.6+')
197 parser.error('--py3k-warnings can only be used on Python 2.6+')
198
198
199 return (options, args)
199 return (options, args)
200
200
201 def rename(src, dst):
201 def rename(src, dst):
202 """Like os.rename(), trade atomicity and opened files friendliness
202 """Like os.rename(), trade atomicity and opened files friendliness
203 for existing destination support.
203 for existing destination support.
204 """
204 """
205 shutil.copy(src, dst)
205 shutil.copy(src, dst)
206 os.remove(src)
206 os.remove(src)
207
207
208 def splitnewlines(text):
208 def splitnewlines(text):
209 '''like str.splitlines, but only split on newlines.
209 '''like str.splitlines, but only split on newlines.
210 keep line endings.'''
210 keep line endings.'''
211 i = 0
211 i = 0
212 lines = []
212 lines = []
213 while True:
213 while True:
214 n = text.find('\n', i)
214 n = text.find('\n', i)
215 if n == -1:
215 if n == -1:
216 last = text[i:]
216 last = text[i:]
217 if last:
217 if last:
218 lines.append(last)
218 lines.append(last)
219 return lines
219 return lines
220 lines.append(text[i:n+1])
220 lines.append(text[i:n+1])
221 i = n + 1
221 i = n + 1
222
222
223 def parsehghaveoutput(lines):
223 def parsehghaveoutput(lines):
224 '''Parse hghave log lines.
224 '''Parse hghave log lines.
225 Return tuple of lists (missing, failed):
225 Return tuple of lists (missing, failed):
226 * the missing/unknown features
226 * the missing/unknown features
227 * the features for which existence check failed'''
227 * the features for which existence check failed'''
228 missing = []
228 missing = []
229 failed = []
229 failed = []
230 for line in lines:
230 for line in lines:
231 if line.startswith(SKIPPED_PREFIX):
231 if line.startswith(SKIPPED_PREFIX):
232 line = line.splitlines()[0]
232 line = line.splitlines()[0]
233 missing.append(line[len(SKIPPED_PREFIX):])
233 missing.append(line[len(SKIPPED_PREFIX):])
234 elif line.startswith(FAILED_PREFIX):
234 elif line.startswith(FAILED_PREFIX):
235 line = line.splitlines()[0]
235 line = line.splitlines()[0]
236 failed.append(line[len(FAILED_PREFIX):])
236 failed.append(line[len(FAILED_PREFIX):])
237
237
238 return missing, failed
238 return missing, failed
239
239
240 def showdiff(expected, output):
240 def showdiff(expected, output):
241 for line in difflib.unified_diff(expected, output,
241 for line in difflib.unified_diff(expected, output,
242 "Expected output", "Test output"):
242 "Expected output", "Test output"):
243 sys.stdout.write(line)
243 sys.stdout.write(line)
244
244
245 def findprogram(program):
245 def findprogram(program):
246 """Search PATH for a executable program"""
246 """Search PATH for a executable program"""
247 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
247 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
248 name = os.path.join(p, program)
248 name = os.path.join(p, program)
249 if os.access(name, os.X_OK):
249 if os.access(name, os.X_OK):
250 return name
250 return name
251 return None
251 return None
252
252
253 def checktools():
253 def checktools():
254 # Before we go any further, check for pre-requisite tools
254 # Before we go any further, check for pre-requisite tools
255 # stuff from coreutils (cat, rm, etc) are not tested
255 # stuff from coreutils (cat, rm, etc) are not tested
256 for p in requiredtools:
256 for p in requiredtools:
257 if os.name == 'nt':
257 if os.name == 'nt':
258 p += '.exe'
258 p += '.exe'
259 found = findprogram(p)
259 found = findprogram(p)
260 if found:
260 if found:
261 vlog("# Found prerequisite", p, "at", found)
261 vlog("# Found prerequisite", p, "at", found)
262 else:
262 else:
263 print "WARNING: Did not find prerequisite tool: "+p
263 print "WARNING: Did not find prerequisite tool: "+p
264
264
265 def cleanup(options):
265 def cleanup(options):
266 if not options.keep_tmpdir:
266 if not options.keep_tmpdir:
267 vlog("# Cleaning up HGTMP", HGTMP)
267 vlog("# Cleaning up HGTMP", HGTMP)
268 shutil.rmtree(HGTMP, True)
268 shutil.rmtree(HGTMP, True)
269
269
270 def usecorrectpython():
270 def usecorrectpython():
271 # some tests run python interpreter. they must use same
271 # some tests run python interpreter. they must use same
272 # interpreter we use or bad things will happen.
272 # interpreter we use or bad things will happen.
273 exedir, exename = os.path.split(sys.executable)
273 exedir, exename = os.path.split(sys.executable)
274 if exename == 'python':
274 if exename == 'python':
275 path = findprogram('python')
275 path = findprogram('python')
276 if os.path.dirname(path) == exedir:
276 if os.path.dirname(path) == exedir:
277 return
277 return
278 vlog('# Making python executable in test path use correct Python')
278 vlog('# Making python executable in test path use correct Python')
279 mypython = os.path.join(BINDIR, 'python')
279 mypython = os.path.join(BINDIR, 'python')
280 try:
280 try:
281 os.symlink(sys.executable, mypython)
281 os.symlink(sys.executable, mypython)
282 except AttributeError:
282 except AttributeError:
283 # windows fallback
283 # windows fallback
284 shutil.copyfile(sys.executable, mypython)
284 shutil.copyfile(sys.executable, mypython)
285 shutil.copymode(sys.executable, mypython)
285 shutil.copymode(sys.executable, mypython)
286
286
287 def installhg(options):
287 def installhg(options):
288 vlog("# Performing temporary installation of HG")
288 vlog("# Performing temporary installation of HG")
289 installerrs = os.path.join("tests", "install.err")
289 installerrs = os.path.join("tests", "install.err")
290 pure = options.pure and "--pure" or ""
290 pure = options.pure and "--pure" or ""
291
291
292 # Run installer in hg root
292 # Run installer in hg root
293 script = os.path.realpath(sys.argv[0])
293 script = os.path.realpath(sys.argv[0])
294 hgroot = os.path.dirname(os.path.dirname(script))
294 hgroot = os.path.dirname(os.path.dirname(script))
295 os.chdir(hgroot)
295 os.chdir(hgroot)
296 nohome = '--home=""'
296 nohome = '--home=""'
297 if os.name == 'nt':
297 if os.name == 'nt':
298 # The --home="" trick works only on OS where os.sep == '/'
298 # The --home="" trick works only on OS where os.sep == '/'
299 # because of a distutils convert_path() fast-path. Avoid it at
299 # because of a distutils convert_path() fast-path. Avoid it at
300 # least on Windows for now, deal with .pydistutils.cfg bugs
300 # least on Windows for now, deal with .pydistutils.cfg bugs
301 # when they happen.
301 # when they happen.
302 nohome = ''
302 nohome = ''
303 cmd = ('%s setup.py %s clean --all'
303 cmd = ('%s setup.py %s clean --all'
304 ' install --force --prefix="%s" --install-lib="%s"'
304 ' install --force --prefix="%s" --install-lib="%s"'
305 ' --install-scripts="%s" %s >%s 2>&1'
305 ' --install-scripts="%s" %s >%s 2>&1'
306 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome,
306 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome,
307 installerrs))
307 installerrs))
308 vlog("# Running", cmd)
308 vlog("# Running", cmd)
309 if os.system(cmd) == 0:
309 if os.system(cmd) == 0:
310 if not options.verbose:
310 if not options.verbose:
311 os.remove(installerrs)
311 os.remove(installerrs)
312 else:
312 else:
313 f = open(installerrs)
313 f = open(installerrs)
314 for line in f:
314 for line in f:
315 print line,
315 print line,
316 f.close()
316 f.close()
317 sys.exit(1)
317 sys.exit(1)
318 os.chdir(TESTDIR)
318 os.chdir(TESTDIR)
319
319
320 usecorrectpython()
320 usecorrectpython()
321
321
322 vlog("# Installing dummy diffstat")
322 vlog("# Installing dummy diffstat")
323 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
323 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
324 f.write('#!' + sys.executable + '\n'
324 f.write('#!' + sys.executable + '\n'
325 'import sys\n'
325 'import sys\n'
326 'files = 0\n'
326 'files = 0\n'
327 'for line in sys.stdin:\n'
327 'for line in sys.stdin:\n'
328 ' if line.startswith("diff "):\n'
328 ' if line.startswith("diff "):\n'
329 ' files += 1\n'
329 ' files += 1\n'
330 'sys.stdout.write("files patched: %d\\n" % files)\n')
330 'sys.stdout.write("files patched: %d\\n" % files)\n')
331 f.close()
331 f.close()
332 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
332 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
333
333
334 if options.py3k_warnings and not options.anycoverage:
334 if options.py3k_warnings and not options.anycoverage:
335 vlog("# Updating hg command to enable Py3k Warnings switch")
335 vlog("# Updating hg command to enable Py3k Warnings switch")
336 f = open(os.path.join(BINDIR, 'hg'), 'r')
336 f = open(os.path.join(BINDIR, 'hg'), 'r')
337 lines = [line.rstrip() for line in f]
337 lines = [line.rstrip() for line in f]
338 lines[0] += ' -3'
338 lines[0] += ' -3'
339 f.close()
339 f.close()
340 f = open(os.path.join(BINDIR, 'hg'), 'w')
340 f = open(os.path.join(BINDIR, 'hg'), 'w')
341 for line in lines:
341 for line in lines:
342 f.write(line + '\n')
342 f.write(line + '\n')
343 f.close()
343 f.close()
344
344
345 if options.anycoverage:
345 if options.anycoverage:
346 vlog("# Installing coverage wrapper")
346 vlog("# Installing coverage wrapper")
347 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
347 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
348 if os.path.exists(COVERAGE_FILE):
348 if os.path.exists(COVERAGE_FILE):
349 os.unlink(COVERAGE_FILE)
349 os.unlink(COVERAGE_FILE)
350 # Create a wrapper script to invoke hg via coverage.py
350 # Create a wrapper script to invoke hg via coverage.py
351 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
351 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
352 f = open(os.path.join(BINDIR, 'hg'), 'w')
352 f = open(os.path.join(BINDIR, 'hg'), 'w')
353 f.write('#!' + sys.executable + '\n')
353 f.write('#!' + sys.executable + '\n')
354 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
354 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
355 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
355 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
356 (os.path.join(TESTDIR, 'coverage.py'),
356 (os.path.join(TESTDIR, 'coverage.py'),
357 os.path.join(BINDIR, '_hg.py')))
357 os.path.join(BINDIR, '_hg.py')))
358 f.close()
358 f.close()
359 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
359 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
360
360
361 def outputcoverage(options):
361 def outputcoverage(options):
362
362
363 vlog('# Producing coverage report')
363 vlog('# Producing coverage report')
364 os.chdir(PYTHONDIR)
364 os.chdir(PYTHONDIR)
365
365
366 def covrun(*args):
366 def covrun(*args):
367 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
367 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
368 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
368 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
369 vlog('# Running: %s' % cmd)
369 vlog('# Running: %s' % cmd)
370 os.system(cmd)
370 os.system(cmd)
371
371
372 omit = [BINDIR, TESTDIR, PYTHONDIR]
372 omit = [BINDIR, TESTDIR, PYTHONDIR]
373 if not options.cover_stdlib:
373 if not options.cover_stdlib:
374 # Exclude as system paths (ignoring empty strings seen on win)
374 # Exclude as system paths (ignoring empty strings seen on win)
375 omit += [x for x in sys.path if x != '']
375 omit += [x for x in sys.path if x != '']
376 omit = ','.join(omit)
376 omit = ','.join(omit)
377
377
378 covrun('-c') # combine from parallel processes
378 covrun('-c') # combine from parallel processes
379 for fn in os.listdir(TESTDIR):
379 for fn in os.listdir(TESTDIR):
380 if fn.startswith('.coverage.'):
380 if fn.startswith('.coverage.'):
381 os.unlink(os.path.join(TESTDIR, fn))
381 os.unlink(os.path.join(TESTDIR, fn))
382
382
383 covrun('-i', '-r', '"--omit=%s"' % omit) # report
383 covrun('-i', '-r', '"--omit=%s"' % omit) # report
384 if options.annotate:
384 if options.annotate:
385 adir = os.path.join(TESTDIR, 'annotated')
385 adir = os.path.join(TESTDIR, 'annotated')
386 if not os.path.isdir(adir):
386 if not os.path.isdir(adir):
387 os.mkdir(adir)
387 os.mkdir(adir)
388 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
388 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
389
389
390 class Timeout(Exception):
390 class Timeout(Exception):
391 pass
391 pass
392
392
393 def alarmed(signum, frame):
393 def alarmed(signum, frame):
394 raise Timeout
394 raise Timeout
395
395
396 def run(cmd, options):
396 def run(cmd, options):
397 """Run command in a sub-process, capturing the output (stdout and stderr).
397 """Run command in a sub-process, capturing the output (stdout and stderr).
398 Return a tuple (exitcode, output). output is None in debug mode."""
398 Return a tuple (exitcode, output). output is None in debug mode."""
399 # TODO: Use subprocess.Popen if we're running on Python 2.4
399 # TODO: Use subprocess.Popen if we're running on Python 2.4
400 if options.debug:
400 if options.debug:
401 proc = subprocess.Popen(cmd, shell=True)
401 proc = subprocess.Popen(cmd, shell=True)
402 ret = proc.wait()
402 ret = proc.wait()
403 return (ret, None)
403 return (ret, None)
404
404
405 if os.name == 'nt' or sys.platform.startswith('java'):
405 if os.name == 'nt' or sys.platform.startswith('java'):
406 tochild, fromchild = os.popen4(cmd)
406 tochild, fromchild = os.popen4(cmd)
407 tochild.close()
407 tochild.close()
408 output = fromchild.read()
408 output = fromchild.read()
409 ret = fromchild.close()
409 ret = fromchild.close()
410 if ret == None:
410 if ret == None:
411 ret = 0
411 ret = 0
412 else:
412 else:
413 proc = Popen4(cmd)
413 proc = Popen4(cmd)
414 try:
414 try:
415 output = ''
415 output = ''
416 proc.tochild.close()
416 proc.tochild.close()
417 output = proc.fromchild.read()
417 output = proc.fromchild.read()
418 ret = proc.wait()
418 ret = proc.wait()
419 if os.WIFEXITED(ret):
419 if os.WIFEXITED(ret):
420 ret = os.WEXITSTATUS(ret)
420 ret = os.WEXITSTATUS(ret)
421 except Timeout:
421 except Timeout:
422 vlog('# Process %d timed out - killing it' % proc.pid)
422 vlog('# Process %d timed out - killing it' % proc.pid)
423 os.kill(proc.pid, signal.SIGTERM)
423 os.kill(proc.pid, signal.SIGTERM)
424 ret = proc.wait()
424 ret = proc.wait()
425 if ret == 0:
425 if ret == 0:
426 ret = signal.SIGTERM << 8
426 ret = signal.SIGTERM << 8
427 output += ("\n### Abort: timeout after %d seconds.\n"
427 output += ("\n### Abort: timeout after %d seconds.\n"
428 % options.timeout)
428 % options.timeout)
429 return ret, splitnewlines(output)
429 return ret, splitnewlines(output)
430
430
431 def runone(options, test, skips, fails):
431 def runone(options, test, skips, fails):
432 '''tristate output:
432 '''tristate output:
433 None -> skipped
433 None -> skipped
434 True -> passed
434 True -> passed
435 False -> failed'''
435 False -> failed'''
436
436
437 def skip(msg):
437 def skip(msg):
438 if not options.verbose:
438 if not options.verbose:
439 skips.append((test, msg))
439 skips.append((test, msg))
440 else:
440 else:
441 print "\nSkipping %s: %s" % (test, msg)
441 print "\nSkipping %s: %s" % (test, msg)
442 return None
442 return None
443
443
444 def fail(msg):
444 def fail(msg):
445 fails.append((test, msg))
445 fails.append((test, msg))
446 if not options.nodiff:
446 if not options.nodiff:
447 print "\nERROR: %s %s" % (test, msg)
447 print "\nERROR: %s %s" % (test, msg)
448 return None
448 return None
449
449
450 vlog("# Test", test)
450 vlog("# Test", test)
451
451
452 # create a fresh hgrc
452 # create a fresh hgrc
453 hgrc = open(HGRCPATH, 'w+')
453 hgrc = open(HGRCPATH, 'w+')
454 hgrc.write('[ui]\n')
454 hgrc.write('[ui]\n')
455 hgrc.write('slash = True\n')
455 hgrc.write('slash = True\n')
456 hgrc.write('[defaults]\n')
456 hgrc.write('[defaults]\n')
457 hgrc.write('backout = -d "0 0"\n')
457 hgrc.write('backout = -d "0 0"\n')
458 hgrc.write('commit = -d "0 0"\n')
458 hgrc.write('commit = -d "0 0"\n')
459 hgrc.write('tag = -d "0 0"\n')
459 hgrc.write('tag = -d "0 0"\n')
460 hgrc.close()
460 hgrc.close()
461
461
462 err = os.path.join(TESTDIR, test+".err")
462 err = os.path.join(TESTDIR, test+".err")
463 ref = os.path.join(TESTDIR, test+".out")
463 ref = os.path.join(TESTDIR, test+".out")
464 testpath = os.path.join(TESTDIR, test)
464 testpath = os.path.join(TESTDIR, test)
465
465
466 if os.path.exists(err):
466 if os.path.exists(err):
467 os.remove(err) # Remove any previous output files
467 os.remove(err) # Remove any previous output files
468
468
469 # Make a tmp subdirectory to work in
469 # Make a tmp subdirectory to work in
470 tmpd = os.path.join(HGTMP, test)
470 tmpd = os.path.join(HGTMP, test)
471 os.mkdir(tmpd)
471 os.mkdir(tmpd)
472 os.chdir(tmpd)
472 os.chdir(tmpd)
473
473
474 try:
474 try:
475 tf = open(testpath)
475 tf = open(testpath)
476 firstline = tf.readline().rstrip()
476 firstline = tf.readline().rstrip()
477 tf.close()
477 tf.close()
478 except:
478 except:
479 firstline = ''
479 firstline = ''
480 lctest = test.lower()
480 lctest = test.lower()
481
481
482 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
482 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
483 py3kswitch = options.py3k_warnings and ' -3' or ''
483 py3kswitch = options.py3k_warnings and ' -3' or ''
484 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
484 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
485 elif lctest.endswith('.bat'):
485 elif lctest.endswith('.bat'):
486 # do not run batch scripts on non-windows
486 # do not run batch scripts on non-windows
487 if os.name != 'nt':
487 if os.name != 'nt':
488 return skip("batch script")
488 return skip("batch script")
489 # To reliably get the error code from batch files on WinXP,
489 # To reliably get the error code from batch files on WinXP,
490 # the "cmd /c call" prefix is needed. Grrr
490 # the "cmd /c call" prefix is needed. Grrr
491 cmd = 'cmd /c call "%s"' % testpath
491 cmd = 'cmd /c call "%s"' % testpath
492 else:
492 else:
493 # do not run shell scripts on windows
493 # do not run shell scripts on windows
494 if os.name == 'nt':
494 if os.name == 'nt':
495 return skip("shell script")
495 return skip("shell script")
496 # do not try to run non-executable programs
496 # do not try to run non-executable programs
497 if not os.path.exists(testpath):
497 if not os.path.exists(testpath):
498 return fail("does not exist")
498 return fail("does not exist")
499 elif not os.access(testpath, os.X_OK):
499 elif not os.access(testpath, os.X_OK):
500 return skip("not executable")
500 return skip("not executable")
501 cmd = '"%s"' % testpath
501 cmd = '"%s"' % testpath
502
502
503 if options.timeout > 0:
503 if options.timeout > 0:
504 signal.alarm(options.timeout)
504 signal.alarm(options.timeout)
505
505
506 vlog("# Running", cmd)
506 vlog("# Running", cmd)
507 ret, out = run(cmd, options)
507 ret, out = run(cmd, options)
508 vlog("# Ret was:", ret)
508 vlog("# Ret was:", ret)
509
509
510 if options.timeout > 0:
510 if options.timeout > 0:
511 signal.alarm(0)
511 signal.alarm(0)
512
512
513 mark = '.'
513 mark = '.'
514
514
515 skipped = (ret == SKIPPED_STATUS)
515 skipped = (ret == SKIPPED_STATUS)
516 # If we're not in --debug mode and reference output file exists,
516 # If we're not in --debug mode and reference output file exists,
517 # check test output against it.
517 # check test output against it.
518 if options.debug:
518 if options.debug:
519 refout = None # to match out == None
519 refout = None # to match out == None
520 elif os.path.exists(ref):
520 elif os.path.exists(ref):
521 f = open(ref, "r")
521 f = open(ref, "r")
522 refout = splitnewlines(f.read())
522 refout = splitnewlines(f.read())
523 f.close()
523 f.close()
524 else:
524 else:
525 refout = []
525 refout = []
526
526
527 if skipped:
527 if skipped:
528 mark = 's'
528 mark = 's'
529 if out is None: # debug mode: nothing to parse
529 if out is None: # debug mode: nothing to parse
530 missing = ['unknown']
530 missing = ['unknown']
531 failed = None
531 failed = None
532 else:
532 else:
533 missing, failed = parsehghaveoutput(out)
533 missing, failed = parsehghaveoutput(out)
534 if not missing:
534 if not missing:
535 missing = ['irrelevant']
535 missing = ['irrelevant']
536 if failed:
536 if failed:
537 fail("hghave failed checking for %s" % failed[-1])
537 fail("hghave failed checking for %s" % failed[-1])
538 skipped = False
538 skipped = False
539 else:
539 else:
540 skip(missing[-1])
540 skip(missing[-1])
541 elif out != refout:
541 elif out != refout:
542 mark = '!'
542 mark = '!'
543 if ret:
543 if ret:
544 fail("output changed and returned error code %d" % ret)
544 fail("output changed and returned error code %d" % ret)
545 else:
545 else:
546 fail("output changed")
546 fail("output changed")
547 if not options.nodiff:
547 if not options.nodiff:
548 showdiff(refout, out)
548 showdiff(refout, out)
549 ret = 1
549 ret = 1
550 elif ret:
550 elif ret:
551 mark = '!'
551 mark = '!'
552 fail("returned error code %d" % ret)
552 fail("returned error code %d" % ret)
553
553
554 if not options.verbose:
554 if not options.verbose:
555 sys.stdout.write(mark)
555 sys.stdout.write(mark)
556 sys.stdout.flush()
556 sys.stdout.flush()
557
557
558 if ret != 0 and not skipped and not options.debug:
558 if ret != 0 and not skipped and not options.debug:
559 # Save errors to a file for diagnosis
559 # Save errors to a file for diagnosis
560 f = open(err, "wb")
560 f = open(err, "wb")
561 for line in out:
561 for line in out:
562 f.write(line)
562 f.write(line)
563 f.close()
563 f.close()
564
564
565 # Kill off any leftover daemon processes
565 # Kill off any leftover daemon processes
566 try:
566 try:
567 fp = open(DAEMON_PIDS)
567 fp = open(DAEMON_PIDS)
568 for line in fp:
568 for line in fp:
569 try:
569 try:
570 pid = int(line)
570 pid = int(line)
571 except ValueError:
571 except ValueError:
572 continue
572 continue
573 try:
573 try:
574 os.kill(pid, 0)
574 os.kill(pid, 0)
575 vlog('# Killing daemon process %d' % pid)
575 vlog('# Killing daemon process %d' % pid)
576 os.kill(pid, signal.SIGTERM)
576 os.kill(pid, signal.SIGTERM)
577 time.sleep(0.25)
577 time.sleep(0.25)
578 os.kill(pid, 0)
578 os.kill(pid, 0)
579 vlog('# Daemon process %d is stuck - really killing it' % pid)
579 vlog('# Daemon process %d is stuck - really killing it' % pid)
580 os.kill(pid, signal.SIGKILL)
580 os.kill(pid, signal.SIGKILL)
581 except OSError, err:
581 except OSError, err:
582 if err.errno != errno.ESRCH:
582 if err.errno != errno.ESRCH:
583 raise
583 raise
584 fp.close()
584 fp.close()
585 os.unlink(DAEMON_PIDS)
585 os.unlink(DAEMON_PIDS)
586 except IOError:
586 except IOError:
587 pass
587 pass
588
588
589 os.chdir(TESTDIR)
589 os.chdir(TESTDIR)
590 if not options.keep_tmpdir:
590 if not options.keep_tmpdir:
591 shutil.rmtree(tmpd, True)
591 shutil.rmtree(tmpd, True)
592 if skipped:
592 if skipped:
593 return None
593 return None
594 return ret == 0
594 return ret == 0
595
595
596 _hgpath = None
596 _hgpath = None
597
597
598 def _gethgpath():
598 def _gethgpath():
599 """Return the path to the mercurial package that is actually found by
599 """Return the path to the mercurial package that is actually found by
600 the current Python interpreter."""
600 the current Python interpreter."""
601 global _hgpath
601 global _hgpath
602 if _hgpath is not None:
602 if _hgpath is not None:
603 return _hgpath
603 return _hgpath
604
604
605 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
605 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
606 pipe = os.popen(cmd % PYTHON)
606 pipe = os.popen(cmd % PYTHON)
607 try:
607 try:
608 _hgpath = pipe.read().strip()
608 _hgpath = pipe.read().strip()
609 finally:
609 finally:
610 pipe.close()
610 pipe.close()
611 return _hgpath
611 return _hgpath
612
612
613 def _checkhglib(verb):
613 def _checkhglib(verb):
614 """Ensure that the 'mercurial' package imported by python is
614 """Ensure that the 'mercurial' package imported by python is
615 the one we expect it to be. If not, print a warning to stderr."""
615 the one we expect it to be. If not, print a warning to stderr."""
616 expecthg = os.path.join(PYTHONDIR, 'mercurial')
616 expecthg = os.path.join(PYTHONDIR, 'mercurial')
617 actualhg = _gethgpath()
617 actualhg = _gethgpath()
618 if actualhg != expecthg:
618 if actualhg != expecthg:
619 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
619 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
620 ' (expected %s)\n'
620 ' (expected %s)\n'
621 % (verb, actualhg, expecthg))
621 % (verb, actualhg, expecthg))
622
622
623 def runchildren(options, tests):
623 def runchildren(options, tests):
624 if INST:
624 if INST:
625 installhg(options)
625 installhg(options)
626 _checkhglib("Testing")
626 _checkhglib("Testing")
627
627
628 optcopy = dict(options.__dict__)
628 optcopy = dict(options.__dict__)
629 optcopy['jobs'] = 1
629 optcopy['jobs'] = 1
630 if optcopy['with_hg'] is None:
630 if optcopy['with_hg'] is None:
631 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
631 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
632 opts = []
632 opts = []
633 for opt, value in optcopy.iteritems():
633 for opt, value in optcopy.iteritems():
634 name = '--' + opt.replace('_', '-')
634 name = '--' + opt.replace('_', '-')
635 if value is True:
635 if value is True:
636 opts.append(name)
636 opts.append(name)
637 elif value is not None:
637 elif value is not None:
638 opts.append(name + '=' + str(value))
638 opts.append(name + '=' + str(value))
639
639
640 tests.reverse()
640 tests.reverse()
641 jobs = [[] for j in xrange(options.jobs)]
641 jobs = [[] for j in xrange(options.jobs)]
642 while tests:
642 while tests:
643 for job in jobs:
643 for job in jobs:
644 if not tests: break
644 if not tests: break
645 job.append(tests.pop())
645 job.append(tests.pop())
646 fps = {}
646 fps = {}
647 for j, job in enumerate(jobs):
647 for j, job in enumerate(jobs):
648 if not job:
648 if not job:
649 continue
649 continue
650 rfd, wfd = os.pipe()
650 rfd, wfd = os.pipe()
651 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
651 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
652 childtmp = os.path.join(HGTMP, 'child%d' % j)
652 childtmp = os.path.join(HGTMP, 'child%d' % j)
653 childopts += ['--tmpdir', childtmp]
653 childopts += ['--tmpdir', childtmp]
654 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
654 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
655 vlog(' '.join(cmdline))
655 vlog(' '.join(cmdline))
656 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
656 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
657 os.close(wfd)
657 os.close(wfd)
658 failures = 0
658 failures = 0
659 tested, skipped, failed = 0, 0, 0
659 tested, skipped, failed = 0, 0, 0
660 skips = []
660 skips = []
661 fails = []
661 fails = []
662 while fps:
662 while fps:
663 pid, status = os.wait()
663 pid, status = os.wait()
664 fp = fps.pop(pid)
664 fp = fps.pop(pid)
665 l = fp.read().splitlines()
665 l = fp.read().splitlines()
666 test, skip, fail = map(int, l[:3])
666 test, skip, fail = map(int, l[:3])
667 split = -fail or len(l)
667 split = -fail or len(l)
668 for s in l[3:split]:
668 for s in l[3:split]:
669 skips.append(s.split(" ", 1))
669 skips.append(s.split(" ", 1))
670 for s in l[split:]:
670 for s in l[split:]:
671 fails.append(s.split(" ", 1))
671 fails.append(s.split(" ", 1))
672 tested += test
672 tested += test
673 skipped += skip
673 skipped += skip
674 failed += fail
674 failed += fail
675 vlog('pid %d exited, status %d' % (pid, status))
675 vlog('pid %d exited, status %d' % (pid, status))
676 failures |= status
676 failures |= status
677 print
677 print
678 if not options.noskips:
678 if not options.noskips:
679 for s in skips:
679 for s in skips:
680 print "Skipped %s: %s" % (s[0], s[1])
680 print "Skipped %s: %s" % (s[0], s[1])
681 for s in fails:
681 for s in fails:
682 print "Failed %s: %s" % (s[0], s[1])
682 print "Failed %s: %s" % (s[0], s[1])
683
683
684 _checkhglib("Tested")
684 _checkhglib("Tested")
685 print "# Ran %d tests, %d skipped, %d failed." % (
685 print "# Ran %d tests, %d skipped, %d failed." % (
686 tested, skipped, failed)
686 tested, skipped, failed)
687 sys.exit(failures != 0)
687 sys.exit(failures != 0)
688
688
689 def runtests(options, tests):
689 def runtests(options, tests):
690 global DAEMON_PIDS, HGRCPATH
690 global DAEMON_PIDS, HGRCPATH
691 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
691 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
692 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
692 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
693
693
694 try:
694 try:
695 if INST:
695 if INST:
696 installhg(options)
696 installhg(options)
697 _checkhglib("Testing")
697 _checkhglib("Testing")
698
698
699 if options.timeout > 0:
699 if options.timeout > 0:
700 try:
700 try:
701 signal.signal(signal.SIGALRM, alarmed)
701 signal.signal(signal.SIGALRM, alarmed)
702 vlog('# Running each test with %d second timeout' %
702 vlog('# Running each test with %d second timeout' %
703 options.timeout)
703 options.timeout)
704 except AttributeError:
704 except AttributeError:
705 print 'WARNING: cannot run tests with timeouts'
705 print 'WARNING: cannot run tests with timeouts'
706 options.timeout = 0
706 options.timeout = 0
707
707
708 tested = 0
708 tested = 0
709 failed = 0
709 failed = 0
710 skipped = 0
710 skipped = 0
711
711
712 if options.restart:
712 if options.restart:
713 orig = list(tests)
713 orig = list(tests)
714 while tests:
714 while tests:
715 if os.path.exists(tests[0] + ".err"):
715 if os.path.exists(tests[0] + ".err"):
716 break
716 break
717 tests.pop(0)
717 tests.pop(0)
718 if not tests:
718 if not tests:
719 print "running all tests"
719 print "running all tests"
720 tests = orig
720 tests = orig
721
721
722 skips = []
722 skips = []
723 fails = []
723 fails = []
724
724
725 for test in tests:
725 for test in tests:
726 if options.retest and not os.path.exists(test + ".err"):
726 if options.retest and not os.path.exists(test + ".err"):
727 skipped += 1
727 skipped += 1
728 continue
728 continue
729
729
730 if options.keywords:
730 if options.keywords:
731 t = open(test).read().lower() + test.lower()
731 t = open(test).read().lower() + test.lower()
732 for k in options.keywords.lower().split():
732 for k in options.keywords.lower().split():
733 if k in t:
733 if k in t:
734 break
734 break
735 else:
735 else:
736 skipped +=1
736 skipped +=1
737 continue
737 continue
738
738
739 ret = runone(options, test, skips, fails)
739 ret = runone(options, test, skips, fails)
740 if ret is None:
740 if ret is None:
741 skipped += 1
741 skipped += 1
742 elif not ret:
742 elif not ret:
743 if options.interactive:
743 if options.interactive:
744 print "Accept this change? [n] ",
744 print "Accept this change? [n] ",
745 answer = sys.stdin.readline().strip()
745 answer = sys.stdin.readline().strip()
746 if answer.lower() in "y yes".split():
746 if answer.lower() in "y yes".split():
747 rename(test + ".err", test + ".out")
747 rename(test + ".err", test + ".out")
748 tested += 1
748 tested += 1
749 fails.pop()
749 fails.pop()
750 continue
750 continue
751 failed += 1
751 failed += 1
752 if options.first:
752 if options.first:
753 break
753 break
754 tested += 1
754 tested += 1
755
755
756 if options.child:
756 if options.child:
757 fp = os.fdopen(options.child, 'w')
757 fp = os.fdopen(options.child, 'w')
758 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
758 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
759 for s in skips:
759 for s in skips:
760 fp.write("%s %s\n" % s)
760 fp.write("%s %s\n" % s)
761 for s in fails:
761 for s in fails:
762 fp.write("%s %s\n" % s)
762 fp.write("%s %s\n" % s)
763 fp.close()
763 fp.close()
764 else:
764 else:
765 print
765 print
766 for s in skips:
766 for s in skips:
767 print "Skipped %s: %s" % s
767 print "Skipped %s: %s" % s
768 for s in fails:
768 for s in fails:
769 print "Failed %s: %s" % s
769 print "Failed %s: %s" % s
770 _checkhglib("Tested")
770 _checkhglib("Tested")
771 print "# Ran %d tests, %d skipped, %d failed." % (
771 print "# Ran %d tests, %d skipped, %d failed." % (
772 tested, skipped, failed)
772 tested, skipped, failed)
773
773
774 if options.anycoverage:
774 if options.anycoverage:
775 outputcoverage(options)
775 outputcoverage(options)
776 except KeyboardInterrupt:
776 except KeyboardInterrupt:
777 failed = True
777 failed = True
778 print "\ninterrupted!"
778 print "\ninterrupted!"
779
779
780 if failed:
780 if failed:
781 sys.exit(1)
781 sys.exit(1)
782
782
783 def main():
783 def main():
784 (options, args) = parseargs()
784 (options, args) = parseargs()
785 if not options.child:
785 if not options.child:
786 os.umask(022)
786 os.umask(022)
787
787
788 checktools()
788 checktools()
789
789
790 # Reset some environment variables to well-known values so that
790 # Reset some environment variables to well-known values so that
791 # the tests produce repeatable output.
791 # the tests produce repeatable output.
792 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
792 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
793 os.environ['TZ'] = 'GMT'
793 os.environ['TZ'] = 'GMT'
794 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
794 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
795 os.environ['CDPATH'] = ''
795 os.environ['CDPATH'] = ''
796 os.environ['COLUMNS'] = '80'
796 os.environ['COLUMNS'] = '80'
797
797
798 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
798 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
799 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
799 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
800 if options.tmpdir:
800 if options.tmpdir:
801 options.keep_tmpdir = True
801 options.keep_tmpdir = True
802 tmpdir = options.tmpdir
802 tmpdir = options.tmpdir
803 if os.path.exists(tmpdir):
803 if os.path.exists(tmpdir):
804 # Meaning of tmpdir has changed since 1.3: we used to create
804 # Meaning of tmpdir has changed since 1.3: we used to create
805 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
805 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
806 # tmpdir already exists.
806 # tmpdir already exists.
807 sys.exit("error: temp dir %r already exists" % tmpdir)
807 sys.exit("error: temp dir %r already exists" % tmpdir)
808
808
809 # Automatically removing tmpdir sounds convenient, but could
809 # Automatically removing tmpdir sounds convenient, but could
810 # really annoy anyone in the habit of using "--tmpdir=/tmp"
810 # really annoy anyone in the habit of using "--tmpdir=/tmp"
811 # or "--tmpdir=$HOME".
811 # or "--tmpdir=$HOME".
812 #vlog("# Removing temp dir", tmpdir)
812 #vlog("# Removing temp dir", tmpdir)
813 #shutil.rmtree(tmpdir)
813 #shutil.rmtree(tmpdir)
814 os.makedirs(tmpdir)
814 os.makedirs(tmpdir)
815 else:
815 else:
816 tmpdir = tempfile.mkdtemp('', 'hgtests.')
816 tmpdir = tempfile.mkdtemp('', 'hgtests.')
817 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
817 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
818 DAEMON_PIDS = None
818 DAEMON_PIDS = None
819 HGRCPATH = None
819 HGRCPATH = None
820
820
821 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
821 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
822 os.environ["HGMERGE"] = "internal:merge"
822 os.environ["HGMERGE"] = "internal:merge"
823 os.environ["HGUSER"] = "test"
823 os.environ["HGUSER"] = "test"
824 os.environ["HGENCODING"] = "ascii"
824 os.environ["HGENCODING"] = "ascii"
825 os.environ["HGENCODINGMODE"] = "strict"
825 os.environ["HGENCODINGMODE"] = "strict"
826 os.environ["HGPORT"] = str(options.port)
826 os.environ["HGPORT"] = str(options.port)
827 os.environ["HGPORT1"] = str(options.port + 1)
827 os.environ["HGPORT1"] = str(options.port + 1)
828 os.environ["HGPORT2"] = str(options.port + 2)
828 os.environ["HGPORT2"] = str(options.port + 2)
829
829
830 if options.with_hg:
830 if options.with_hg:
831 INST = None
831 INST = None
832 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
832 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
833
833
834 # This looks redundant with how Python initializes sys.path from
834 # This looks redundant with how Python initializes sys.path from
835 # the location of the script being executed. Needed because the
835 # the location of the script being executed. Needed because the
836 # "hg" specified by --with-hg is not the only Python script
836 # "hg" specified by --with-hg is not the only Python script
837 # executed in the test suite that needs to import 'mercurial'
837 # executed in the test suite that needs to import 'mercurial'
838 # ... which means it's not really redundant at all.
838 # ... which means it's not really redundant at all.
839 PYTHONDIR = BINDIR
839 PYTHONDIR = BINDIR
840 else:
840 else:
841 INST = os.path.join(HGTMP, "install")
841 INST = os.path.join(HGTMP, "install")
842 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
842 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
843 PYTHONDIR = os.path.join(INST, "lib", "python")
843 PYTHONDIR = os.path.join(INST, "lib", "python")
844
844
845 os.environ["BINDIR"] = BINDIR
845 os.environ["BINDIR"] = BINDIR
846 os.environ["PYTHON"] = PYTHON
846 os.environ["PYTHON"] = PYTHON
847
847
848 if not options.child:
848 if not options.child:
849 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
849 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
850 os.environ["PATH"] = os.pathsep.join(path)
850 os.environ["PATH"] = os.pathsep.join(path)
851
851
852 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
852 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
853 # can run .../tests/run-tests.py test-foo where test-foo
853 # can run .../tests/run-tests.py test-foo where test-foo
854 # adds an extension to HGRC
854 # adds an extension to HGRC
855 pypath = [PYTHONDIR, TESTDIR]
855 pypath = [PYTHONDIR, TESTDIR]
856 # We have to augment PYTHONPATH, rather than simply replacing
856 # We have to augment PYTHONPATH, rather than simply replacing
857 # it, in case external libraries are only available via current
857 # it, in case external libraries are only available via current
858 # PYTHONPATH. (In particular, the Subversion bindings on OS X
858 # PYTHONPATH. (In particular, the Subversion bindings on OS X
859 # are in /opt/subversion.)
859 # are in /opt/subversion.)
860 oldpypath = os.environ.get('PYTHONPATH')
860 oldpypath = os.environ.get('PYTHONPATH')
861 if oldpypath:
861 if oldpypath:
862 pypath.append(oldpypath)
862 pypath.append(oldpypath)
863 os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
863 os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
864
864
865 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
865 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
866
866
867 if len(args) == 0:
867 if len(args) == 0:
868 args = os.listdir(".")
868 args = os.listdir(".")
869 args.sort()
869 args.sort()
870
870
871 tests = []
871 tests = []
872 for test in args:
872 for test in args:
873 if (test.startswith("test-") and '~' not in test and
873 if (test.startswith("test-") and '~' not in test and
874 ('.' not in test or test.endswith('.py') or
874 ('.' not in test or test.endswith('.py') or
875 test.endswith('.bat'))):
875 test.endswith('.bat'))):
876 tests.append(test)
876 tests.append(test)
877 if not tests:
877 if not tests:
878 print "# Ran 0 tests, 0 skipped, 0 failed."
878 print "# Ran 0 tests, 0 skipped, 0 failed."
879 return
879 return
880
880
881 vlog("# Using TESTDIR", TESTDIR)
881 vlog("# Using TESTDIR", TESTDIR)
882 vlog("# Using HGTMP", HGTMP)
882 vlog("# Using HGTMP", HGTMP)
883 vlog("# Using PATH", os.environ["PATH"])
883 vlog("# Using PATH", os.environ["PATH"])
884 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
884 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
885
885
886 try:
886 try:
887 if len(tests) > 1 and options.jobs > 1:
887 if len(tests) > 1 and options.jobs > 1:
888 runchildren(options, tests)
888 runchildren(options, tests)
889 else:
889 else:
890 runtests(options, tests)
890 runtests(options, tests)
891 finally:
891 finally:
892 cleanup(options)
892 cleanup(options)
893
893
894 main()
894 main()
General Comments 0
You need to be logged in to leave comments. Login now