##// END OF EJS Templates
run-tests: work around a distutils bug triggered by 0a8a43b4ca75
Patrick Mezard -
r9905:95517eb3 default
parent child Browse files
Show More
@@ -1,885 +1,893 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=""'
297 if os.name == 'nt':
298 # The --home="" trick works only on OS where os.sep == '/'
299 # because of a distutils convert_path() fast-path. Avoid it at
300 # least on Windows for now, deal with .pydistutils.cfg bugs
301 # when they happen.
302 nohome = ''
296 cmd = ('%s setup.py %s clean --all'
303 cmd = ('%s setup.py %s clean --all'
297 ' install --force --prefix="%s" --install-lib="%s"'
304 ' install --force --prefix="%s" --install-lib="%s"'
298 ' --install-scripts="%s" --home="" >%s 2>&1'
305 ' --install-scripts="%s" %s >%s 2>&1'
299 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
306 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome,
307 installerrs))
300 vlog("# Running", cmd)
308 vlog("# Running", cmd)
301 if os.system(cmd) == 0:
309 if os.system(cmd) == 0:
302 if not options.verbose:
310 if not options.verbose:
303 os.remove(installerrs)
311 os.remove(installerrs)
304 else:
312 else:
305 f = open(installerrs)
313 f = open(installerrs)
306 for line in f:
314 for line in f:
307 print line,
315 print line,
308 f.close()
316 f.close()
309 sys.exit(1)
317 sys.exit(1)
310 os.chdir(TESTDIR)
318 os.chdir(TESTDIR)
311
319
312 usecorrectpython()
320 usecorrectpython()
313
321
314 vlog("# Installing dummy diffstat")
322 vlog("# Installing dummy diffstat")
315 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
323 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
316 f.write('#!' + sys.executable + '\n'
324 f.write('#!' + sys.executable + '\n'
317 'import sys\n'
325 'import sys\n'
318 'files = 0\n'
326 'files = 0\n'
319 'for line in sys.stdin:\n'
327 'for line in sys.stdin:\n'
320 ' if line.startswith("diff "):\n'
328 ' if line.startswith("diff "):\n'
321 ' files += 1\n'
329 ' files += 1\n'
322 'sys.stdout.write("files patched: %d\\n" % files)\n')
330 'sys.stdout.write("files patched: %d\\n" % files)\n')
323 f.close()
331 f.close()
324 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
332 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
325
333
326 if options.py3k_warnings and not options.anycoverage:
334 if options.py3k_warnings and not options.anycoverage:
327 vlog("# Updating hg command to enable Py3k Warnings switch")
335 vlog("# Updating hg command to enable Py3k Warnings switch")
328 f = open(os.path.join(BINDIR, 'hg'), 'r')
336 f = open(os.path.join(BINDIR, 'hg'), 'r')
329 lines = [line.rstrip() for line in f]
337 lines = [line.rstrip() for line in f]
330 lines[0] += ' -3'
338 lines[0] += ' -3'
331 f.close()
339 f.close()
332 f = open(os.path.join(BINDIR, 'hg'), 'w')
340 f = open(os.path.join(BINDIR, 'hg'), 'w')
333 for line in lines:
341 for line in lines:
334 f.write(line + '\n')
342 f.write(line + '\n')
335 f.close()
343 f.close()
336
344
337 if options.anycoverage:
345 if options.anycoverage:
338 vlog("# Installing coverage wrapper")
346 vlog("# Installing coverage wrapper")
339 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
347 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
340 if os.path.exists(COVERAGE_FILE):
348 if os.path.exists(COVERAGE_FILE):
341 os.unlink(COVERAGE_FILE)
349 os.unlink(COVERAGE_FILE)
342 # Create a wrapper script to invoke hg via coverage.py
350 # Create a wrapper script to invoke hg via coverage.py
343 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"))
344 f = open(os.path.join(BINDIR, 'hg'), 'w')
352 f = open(os.path.join(BINDIR, 'hg'), 'w')
345 f.write('#!' + sys.executable + '\n')
353 f.write('#!' + sys.executable + '\n')
346 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
354 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
347 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
355 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
348 (os.path.join(TESTDIR, 'coverage.py'),
356 (os.path.join(TESTDIR, 'coverage.py'),
349 os.path.join(BINDIR, '_hg.py')))
357 os.path.join(BINDIR, '_hg.py')))
350 f.close()
358 f.close()
351 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
359 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
352
360
353 def outputcoverage(options):
361 def outputcoverage(options):
354
362
355 vlog('# Producing coverage report')
363 vlog('# Producing coverage report')
356 os.chdir(PYTHONDIR)
364 os.chdir(PYTHONDIR)
357
365
358 def covrun(*args):
366 def covrun(*args):
359 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
367 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
360 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
368 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
361 vlog('# Running: %s' % cmd)
369 vlog('# Running: %s' % cmd)
362 os.system(cmd)
370 os.system(cmd)
363
371
364 omit = [BINDIR, TESTDIR, PYTHONDIR]
372 omit = [BINDIR, TESTDIR, PYTHONDIR]
365 if not options.cover_stdlib:
373 if not options.cover_stdlib:
366 # Exclude as system paths (ignoring empty strings seen on win)
374 # Exclude as system paths (ignoring empty strings seen on win)
367 omit += [x for x in sys.path if x != '']
375 omit += [x for x in sys.path if x != '']
368 omit = ','.join(omit)
376 omit = ','.join(omit)
369
377
370 covrun('-c') # combine from parallel processes
378 covrun('-c') # combine from parallel processes
371 for fn in os.listdir(TESTDIR):
379 for fn in os.listdir(TESTDIR):
372 if fn.startswith('.coverage.'):
380 if fn.startswith('.coverage.'):
373 os.unlink(os.path.join(TESTDIR, fn))
381 os.unlink(os.path.join(TESTDIR, fn))
374
382
375 covrun('-i', '-r', '"--omit=%s"' % omit) # report
383 covrun('-i', '-r', '"--omit=%s"' % omit) # report
376 if options.annotate:
384 if options.annotate:
377 adir = os.path.join(TESTDIR, 'annotated')
385 adir = os.path.join(TESTDIR, 'annotated')
378 if not os.path.isdir(adir):
386 if not os.path.isdir(adir):
379 os.mkdir(adir)
387 os.mkdir(adir)
380 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
388 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
381
389
382 class Timeout(Exception):
390 class Timeout(Exception):
383 pass
391 pass
384
392
385 def alarmed(signum, frame):
393 def alarmed(signum, frame):
386 raise Timeout
394 raise Timeout
387
395
388 def run(cmd, options):
396 def run(cmd, options):
389 """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).
390 Return a tuple (exitcode, output). output is None in debug mode."""
398 Return a tuple (exitcode, output). output is None in debug mode."""
391 # 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
392 if options.debug:
400 if options.debug:
393 proc = subprocess.Popen(cmd, shell=True)
401 proc = subprocess.Popen(cmd, shell=True)
394 ret = proc.wait()
402 ret = proc.wait()
395 return (ret, None)
403 return (ret, None)
396
404
397 if os.name == 'nt' or sys.platform.startswith('java'):
405 if os.name == 'nt' or sys.platform.startswith('java'):
398 tochild, fromchild = os.popen4(cmd)
406 tochild, fromchild = os.popen4(cmd)
399 tochild.close()
407 tochild.close()
400 output = fromchild.read()
408 output = fromchild.read()
401 ret = fromchild.close()
409 ret = fromchild.close()
402 if ret == None:
410 if ret == None:
403 ret = 0
411 ret = 0
404 else:
412 else:
405 proc = Popen4(cmd)
413 proc = Popen4(cmd)
406 try:
414 try:
407 output = ''
415 output = ''
408 proc.tochild.close()
416 proc.tochild.close()
409 output = proc.fromchild.read()
417 output = proc.fromchild.read()
410 ret = proc.wait()
418 ret = proc.wait()
411 if os.WIFEXITED(ret):
419 if os.WIFEXITED(ret):
412 ret = os.WEXITSTATUS(ret)
420 ret = os.WEXITSTATUS(ret)
413 except Timeout:
421 except Timeout:
414 vlog('# Process %d timed out - killing it' % proc.pid)
422 vlog('# Process %d timed out - killing it' % proc.pid)
415 os.kill(proc.pid, signal.SIGTERM)
423 os.kill(proc.pid, signal.SIGTERM)
416 ret = proc.wait()
424 ret = proc.wait()
417 if ret == 0:
425 if ret == 0:
418 ret = signal.SIGTERM << 8
426 ret = signal.SIGTERM << 8
419 output += ("\n### Abort: timeout after %d seconds.\n"
427 output += ("\n### Abort: timeout after %d seconds.\n"
420 % options.timeout)
428 % options.timeout)
421 return ret, splitnewlines(output)
429 return ret, splitnewlines(output)
422
430
423 def runone(options, test, skips, fails):
431 def runone(options, test, skips, fails):
424 '''tristate output:
432 '''tristate output:
425 None -> skipped
433 None -> skipped
426 True -> passed
434 True -> passed
427 False -> failed'''
435 False -> failed'''
428
436
429 def skip(msg):
437 def skip(msg):
430 if not options.verbose:
438 if not options.verbose:
431 skips.append((test, msg))
439 skips.append((test, msg))
432 else:
440 else:
433 print "\nSkipping %s: %s" % (test, msg)
441 print "\nSkipping %s: %s" % (test, msg)
434 return None
442 return None
435
443
436 def fail(msg):
444 def fail(msg):
437 fails.append((test, msg))
445 fails.append((test, msg))
438 if not options.nodiff:
446 if not options.nodiff:
439 print "\nERROR: %s %s" % (test, msg)
447 print "\nERROR: %s %s" % (test, msg)
440 return None
448 return None
441
449
442 vlog("# Test", test)
450 vlog("# Test", test)
443
451
444 # create a fresh hgrc
452 # create a fresh hgrc
445 hgrc = open(HGRCPATH, 'w+')
453 hgrc = open(HGRCPATH, 'w+')
446 hgrc.write('[ui]\n')
454 hgrc.write('[ui]\n')
447 hgrc.write('slash = True\n')
455 hgrc.write('slash = True\n')
448 hgrc.write('[defaults]\n')
456 hgrc.write('[defaults]\n')
449 hgrc.write('backout = -d "0 0"\n')
457 hgrc.write('backout = -d "0 0"\n')
450 hgrc.write('commit = -d "0 0"\n')
458 hgrc.write('commit = -d "0 0"\n')
451 hgrc.write('tag = -d "0 0"\n')
459 hgrc.write('tag = -d "0 0"\n')
452 hgrc.close()
460 hgrc.close()
453
461
454 err = os.path.join(TESTDIR, test+".err")
462 err = os.path.join(TESTDIR, test+".err")
455 ref = os.path.join(TESTDIR, test+".out")
463 ref = os.path.join(TESTDIR, test+".out")
456 testpath = os.path.join(TESTDIR, test)
464 testpath = os.path.join(TESTDIR, test)
457
465
458 if os.path.exists(err):
466 if os.path.exists(err):
459 os.remove(err) # Remove any previous output files
467 os.remove(err) # Remove any previous output files
460
468
461 # Make a tmp subdirectory to work in
469 # Make a tmp subdirectory to work in
462 tmpd = os.path.join(HGTMP, test)
470 tmpd = os.path.join(HGTMP, test)
463 os.mkdir(tmpd)
471 os.mkdir(tmpd)
464 os.chdir(tmpd)
472 os.chdir(tmpd)
465
473
466 try:
474 try:
467 tf = open(testpath)
475 tf = open(testpath)
468 firstline = tf.readline().rstrip()
476 firstline = tf.readline().rstrip()
469 tf.close()
477 tf.close()
470 except:
478 except:
471 firstline = ''
479 firstline = ''
472 lctest = test.lower()
480 lctest = test.lower()
473
481
474 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
482 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
475 py3kswitch = options.py3k_warnings and ' -3' or ''
483 py3kswitch = options.py3k_warnings and ' -3' or ''
476 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
484 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
477 elif lctest.endswith('.bat'):
485 elif lctest.endswith('.bat'):
478 # do not run batch scripts on non-windows
486 # do not run batch scripts on non-windows
479 if os.name != 'nt':
487 if os.name != 'nt':
480 return skip("batch script")
488 return skip("batch script")
481 # To reliably get the error code from batch files on WinXP,
489 # To reliably get the error code from batch files on WinXP,
482 # the "cmd /c call" prefix is needed. Grrr
490 # the "cmd /c call" prefix is needed. Grrr
483 cmd = 'cmd /c call "%s"' % testpath
491 cmd = 'cmd /c call "%s"' % testpath
484 else:
492 else:
485 # do not run shell scripts on windows
493 # do not run shell scripts on windows
486 if os.name == 'nt':
494 if os.name == 'nt':
487 return skip("shell script")
495 return skip("shell script")
488 # do not try to run non-executable programs
496 # do not try to run non-executable programs
489 if not os.path.exists(testpath):
497 if not os.path.exists(testpath):
490 return fail("does not exist")
498 return fail("does not exist")
491 elif not os.access(testpath, os.X_OK):
499 elif not os.access(testpath, os.X_OK):
492 return skip("not executable")
500 return skip("not executable")
493 cmd = '"%s"' % testpath
501 cmd = '"%s"' % testpath
494
502
495 if options.timeout > 0:
503 if options.timeout > 0:
496 signal.alarm(options.timeout)
504 signal.alarm(options.timeout)
497
505
498 vlog("# Running", cmd)
506 vlog("# Running", cmd)
499 ret, out = run(cmd, options)
507 ret, out = run(cmd, options)
500 vlog("# Ret was:", ret)
508 vlog("# Ret was:", ret)
501
509
502 if options.timeout > 0:
510 if options.timeout > 0:
503 signal.alarm(0)
511 signal.alarm(0)
504
512
505 mark = '.'
513 mark = '.'
506
514
507 skipped = (ret == SKIPPED_STATUS)
515 skipped = (ret == SKIPPED_STATUS)
508 # 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,
509 # check test output against it.
517 # check test output against it.
510 if options.debug:
518 if options.debug:
511 refout = None # to match out == None
519 refout = None # to match out == None
512 elif os.path.exists(ref):
520 elif os.path.exists(ref):
513 f = open(ref, "r")
521 f = open(ref, "r")
514 refout = splitnewlines(f.read())
522 refout = splitnewlines(f.read())
515 f.close()
523 f.close()
516 else:
524 else:
517 refout = []
525 refout = []
518
526
519 if skipped:
527 if skipped:
520 mark = 's'
528 mark = 's'
521 if out is None: # debug mode: nothing to parse
529 if out is None: # debug mode: nothing to parse
522 missing = ['unknown']
530 missing = ['unknown']
523 failed = None
531 failed = None
524 else:
532 else:
525 missing, failed = parsehghaveoutput(out)
533 missing, failed = parsehghaveoutput(out)
526 if not missing:
534 if not missing:
527 missing = ['irrelevant']
535 missing = ['irrelevant']
528 if failed:
536 if failed:
529 fail("hghave failed checking for %s" % failed[-1])
537 fail("hghave failed checking for %s" % failed[-1])
530 skipped = False
538 skipped = False
531 else:
539 else:
532 skip(missing[-1])
540 skip(missing[-1])
533 elif out != refout:
541 elif out != refout:
534 mark = '!'
542 mark = '!'
535 if ret:
543 if ret:
536 fail("output changed and returned error code %d" % ret)
544 fail("output changed and returned error code %d" % ret)
537 else:
545 else:
538 fail("output changed")
546 fail("output changed")
539 if not options.nodiff:
547 if not options.nodiff:
540 showdiff(refout, out)
548 showdiff(refout, out)
541 ret = 1
549 ret = 1
542 elif ret:
550 elif ret:
543 mark = '!'
551 mark = '!'
544 fail("returned error code %d" % ret)
552 fail("returned error code %d" % ret)
545
553
546 if not options.verbose:
554 if not options.verbose:
547 sys.stdout.write(mark)
555 sys.stdout.write(mark)
548 sys.stdout.flush()
556 sys.stdout.flush()
549
557
550 if ret != 0 and not skipped and not options.debug:
558 if ret != 0 and not skipped and not options.debug:
551 # Save errors to a file for diagnosis
559 # Save errors to a file for diagnosis
552 f = open(err, "wb")
560 f = open(err, "wb")
553 for line in out:
561 for line in out:
554 f.write(line)
562 f.write(line)
555 f.close()
563 f.close()
556
564
557 # Kill off any leftover daemon processes
565 # Kill off any leftover daemon processes
558 try:
566 try:
559 fp = open(DAEMON_PIDS)
567 fp = open(DAEMON_PIDS)
560 for line in fp:
568 for line in fp:
561 try:
569 try:
562 pid = int(line)
570 pid = int(line)
563 except ValueError:
571 except ValueError:
564 continue
572 continue
565 try:
573 try:
566 os.kill(pid, 0)
574 os.kill(pid, 0)
567 vlog('# Killing daemon process %d' % pid)
575 vlog('# Killing daemon process %d' % pid)
568 os.kill(pid, signal.SIGTERM)
576 os.kill(pid, signal.SIGTERM)
569 time.sleep(0.25)
577 time.sleep(0.25)
570 os.kill(pid, 0)
578 os.kill(pid, 0)
571 vlog('# Daemon process %d is stuck - really killing it' % pid)
579 vlog('# Daemon process %d is stuck - really killing it' % pid)
572 os.kill(pid, signal.SIGKILL)
580 os.kill(pid, signal.SIGKILL)
573 except OSError, err:
581 except OSError, err:
574 if err.errno != errno.ESRCH:
582 if err.errno != errno.ESRCH:
575 raise
583 raise
576 fp.close()
584 fp.close()
577 os.unlink(DAEMON_PIDS)
585 os.unlink(DAEMON_PIDS)
578 except IOError:
586 except IOError:
579 pass
587 pass
580
588
581 os.chdir(TESTDIR)
589 os.chdir(TESTDIR)
582 if not options.keep_tmpdir:
590 if not options.keep_tmpdir:
583 shutil.rmtree(tmpd, True)
591 shutil.rmtree(tmpd, True)
584 if skipped:
592 if skipped:
585 return None
593 return None
586 return ret == 0
594 return ret == 0
587
595
588 _hgpath = None
596 _hgpath = None
589
597
590 def _gethgpath():
598 def _gethgpath():
591 """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
592 the current Python interpreter."""
600 the current Python interpreter."""
593 global _hgpath
601 global _hgpath
594 if _hgpath is not None:
602 if _hgpath is not None:
595 return _hgpath
603 return _hgpath
596
604
597 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
605 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
598 pipe = os.popen(cmd % PYTHON)
606 pipe = os.popen(cmd % PYTHON)
599 try:
607 try:
600 _hgpath = pipe.read().strip()
608 _hgpath = pipe.read().strip()
601 finally:
609 finally:
602 pipe.close()
610 pipe.close()
603 return _hgpath
611 return _hgpath
604
612
605 def _checkhglib(verb):
613 def _checkhglib(verb):
606 """Ensure that the 'mercurial' package imported by python is
614 """Ensure that the 'mercurial' package imported by python is
607 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."""
608 expecthg = os.path.join(PYTHONDIR, 'mercurial')
616 expecthg = os.path.join(PYTHONDIR, 'mercurial')
609 actualhg = _gethgpath()
617 actualhg = _gethgpath()
610 if actualhg != expecthg:
618 if actualhg != expecthg:
611 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
619 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
612 ' (expected %s)\n'
620 ' (expected %s)\n'
613 % (verb, actualhg, expecthg))
621 % (verb, actualhg, expecthg))
614
622
615 def runchildren(options, tests):
623 def runchildren(options, tests):
616 if INST:
624 if INST:
617 installhg(options)
625 installhg(options)
618 _checkhglib("Testing")
626 _checkhglib("Testing")
619
627
620 optcopy = dict(options.__dict__)
628 optcopy = dict(options.__dict__)
621 optcopy['jobs'] = 1
629 optcopy['jobs'] = 1
622 if optcopy['with_hg'] is None:
630 if optcopy['with_hg'] is None:
623 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
631 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
624 opts = []
632 opts = []
625 for opt, value in optcopy.iteritems():
633 for opt, value in optcopy.iteritems():
626 name = '--' + opt.replace('_', '-')
634 name = '--' + opt.replace('_', '-')
627 if value is True:
635 if value is True:
628 opts.append(name)
636 opts.append(name)
629 elif value is not None:
637 elif value is not None:
630 opts.append(name + '=' + str(value))
638 opts.append(name + '=' + str(value))
631
639
632 tests.reverse()
640 tests.reverse()
633 jobs = [[] for j in xrange(options.jobs)]
641 jobs = [[] for j in xrange(options.jobs)]
634 while tests:
642 while tests:
635 for job in jobs:
643 for job in jobs:
636 if not tests: break
644 if not tests: break
637 job.append(tests.pop())
645 job.append(tests.pop())
638 fps = {}
646 fps = {}
639 for j, job in enumerate(jobs):
647 for j, job in enumerate(jobs):
640 if not job:
648 if not job:
641 continue
649 continue
642 rfd, wfd = os.pipe()
650 rfd, wfd = os.pipe()
643 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
651 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
644 childtmp = os.path.join(HGTMP, 'child%d' % j)
652 childtmp = os.path.join(HGTMP, 'child%d' % j)
645 childopts += ['--tmpdir', childtmp]
653 childopts += ['--tmpdir', childtmp]
646 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
654 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
647 vlog(' '.join(cmdline))
655 vlog(' '.join(cmdline))
648 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')
649 os.close(wfd)
657 os.close(wfd)
650 failures = 0
658 failures = 0
651 tested, skipped, failed = 0, 0, 0
659 tested, skipped, failed = 0, 0, 0
652 skips = []
660 skips = []
653 fails = []
661 fails = []
654 while fps:
662 while fps:
655 pid, status = os.wait()
663 pid, status = os.wait()
656 fp = fps.pop(pid)
664 fp = fps.pop(pid)
657 l = fp.read().splitlines()
665 l = fp.read().splitlines()
658 test, skip, fail = map(int, l[:3])
666 test, skip, fail = map(int, l[:3])
659 split = -fail or len(l)
667 split = -fail or len(l)
660 for s in l[3:split]:
668 for s in l[3:split]:
661 skips.append(s.split(" ", 1))
669 skips.append(s.split(" ", 1))
662 for s in l[split:]:
670 for s in l[split:]:
663 fails.append(s.split(" ", 1))
671 fails.append(s.split(" ", 1))
664 tested += test
672 tested += test
665 skipped += skip
673 skipped += skip
666 failed += fail
674 failed += fail
667 vlog('pid %d exited, status %d' % (pid, status))
675 vlog('pid %d exited, status %d' % (pid, status))
668 failures |= status
676 failures |= status
669 print
677 print
670 if not options.noskips:
678 if not options.noskips:
671 for s in skips:
679 for s in skips:
672 print "Skipped %s: %s" % (s[0], s[1])
680 print "Skipped %s: %s" % (s[0], s[1])
673 for s in fails:
681 for s in fails:
674 print "Failed %s: %s" % (s[0], s[1])
682 print "Failed %s: %s" % (s[0], s[1])
675
683
676 _checkhglib("Tested")
684 _checkhglib("Tested")
677 print "# Ran %d tests, %d skipped, %d failed." % (
685 print "# Ran %d tests, %d skipped, %d failed." % (
678 tested, skipped, failed)
686 tested, skipped, failed)
679 sys.exit(failures != 0)
687 sys.exit(failures != 0)
680
688
681 def runtests(options, tests):
689 def runtests(options, tests):
682 global DAEMON_PIDS, HGRCPATH
690 global DAEMON_PIDS, HGRCPATH
683 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')
684 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
692 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
685
693
686 try:
694 try:
687 if INST:
695 if INST:
688 installhg(options)
696 installhg(options)
689 _checkhglib("Testing")
697 _checkhglib("Testing")
690
698
691 if options.timeout > 0:
699 if options.timeout > 0:
692 try:
700 try:
693 signal.signal(signal.SIGALRM, alarmed)
701 signal.signal(signal.SIGALRM, alarmed)
694 vlog('# Running each test with %d second timeout' %
702 vlog('# Running each test with %d second timeout' %
695 options.timeout)
703 options.timeout)
696 except AttributeError:
704 except AttributeError:
697 print 'WARNING: cannot run tests with timeouts'
705 print 'WARNING: cannot run tests with timeouts'
698 options.timeout = 0
706 options.timeout = 0
699
707
700 tested = 0
708 tested = 0
701 failed = 0
709 failed = 0
702 skipped = 0
710 skipped = 0
703
711
704 if options.restart:
712 if options.restart:
705 orig = list(tests)
713 orig = list(tests)
706 while tests:
714 while tests:
707 if os.path.exists(tests[0] + ".err"):
715 if os.path.exists(tests[0] + ".err"):
708 break
716 break
709 tests.pop(0)
717 tests.pop(0)
710 if not tests:
718 if not tests:
711 print "running all tests"
719 print "running all tests"
712 tests = orig
720 tests = orig
713
721
714 skips = []
722 skips = []
715 fails = []
723 fails = []
716
724
717 for test in tests:
725 for test in tests:
718 if options.retest and not os.path.exists(test + ".err"):
726 if options.retest and not os.path.exists(test + ".err"):
719 skipped += 1
727 skipped += 1
720 continue
728 continue
721
729
722 if options.keywords:
730 if options.keywords:
723 t = open(test).read().lower() + test.lower()
731 t = open(test).read().lower() + test.lower()
724 for k in options.keywords.lower().split():
732 for k in options.keywords.lower().split():
725 if k in t:
733 if k in t:
726 break
734 break
727 else:
735 else:
728 skipped +=1
736 skipped +=1
729 continue
737 continue
730
738
731 ret = runone(options, test, skips, fails)
739 ret = runone(options, test, skips, fails)
732 if ret is None:
740 if ret is None:
733 skipped += 1
741 skipped += 1
734 elif not ret:
742 elif not ret:
735 if options.interactive:
743 if options.interactive:
736 print "Accept this change? [n] ",
744 print "Accept this change? [n] ",
737 answer = sys.stdin.readline().strip()
745 answer = sys.stdin.readline().strip()
738 if answer.lower() in "y yes".split():
746 if answer.lower() in "y yes".split():
739 rename(test + ".err", test + ".out")
747 rename(test + ".err", test + ".out")
740 tested += 1
748 tested += 1
741 fails.pop()
749 fails.pop()
742 continue
750 continue
743 failed += 1
751 failed += 1
744 if options.first:
752 if options.first:
745 break
753 break
746 tested += 1
754 tested += 1
747
755
748 if options.child:
756 if options.child:
749 fp = os.fdopen(options.child, 'w')
757 fp = os.fdopen(options.child, 'w')
750 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
758 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
751 for s in skips:
759 for s in skips:
752 fp.write("%s %s\n" % s)
760 fp.write("%s %s\n" % s)
753 for s in fails:
761 for s in fails:
754 fp.write("%s %s\n" % s)
762 fp.write("%s %s\n" % s)
755 fp.close()
763 fp.close()
756 else:
764 else:
757 print
765 print
758 for s in skips:
766 for s in skips:
759 print "Skipped %s: %s" % s
767 print "Skipped %s: %s" % s
760 for s in fails:
768 for s in fails:
761 print "Failed %s: %s" % s
769 print "Failed %s: %s" % s
762 _checkhglib("Tested")
770 _checkhglib("Tested")
763 print "# Ran %d tests, %d skipped, %d failed." % (
771 print "# Ran %d tests, %d skipped, %d failed." % (
764 tested, skipped, failed)
772 tested, skipped, failed)
765
773
766 if options.anycoverage:
774 if options.anycoverage:
767 outputcoverage(options)
775 outputcoverage(options)
768 except KeyboardInterrupt:
776 except KeyboardInterrupt:
769 failed = True
777 failed = True
770 print "\ninterrupted!"
778 print "\ninterrupted!"
771
779
772 if failed:
780 if failed:
773 sys.exit(1)
781 sys.exit(1)
774
782
775 def main():
783 def main():
776 (options, args) = parseargs()
784 (options, args) = parseargs()
777 if not options.child:
785 if not options.child:
778 os.umask(022)
786 os.umask(022)
779
787
780 checktools()
788 checktools()
781
789
782 # Reset some environment variables to well-known values so that
790 # Reset some environment variables to well-known values so that
783 # the tests produce repeatable output.
791 # the tests produce repeatable output.
784 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
792 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
785 os.environ['TZ'] = 'GMT'
793 os.environ['TZ'] = 'GMT'
786 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
794 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
787 os.environ['CDPATH'] = ''
795 os.environ['CDPATH'] = ''
788
796
789 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
797 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
790 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
798 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
791 if options.tmpdir:
799 if options.tmpdir:
792 options.keep_tmpdir = True
800 options.keep_tmpdir = True
793 tmpdir = options.tmpdir
801 tmpdir = options.tmpdir
794 if os.path.exists(tmpdir):
802 if os.path.exists(tmpdir):
795 # Meaning of tmpdir has changed since 1.3: we used to create
803 # Meaning of tmpdir has changed since 1.3: we used to create
796 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
804 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
797 # tmpdir already exists.
805 # tmpdir already exists.
798 sys.exit("error: temp dir %r already exists" % tmpdir)
806 sys.exit("error: temp dir %r already exists" % tmpdir)
799
807
800 # Automatically removing tmpdir sounds convenient, but could
808 # Automatically removing tmpdir sounds convenient, but could
801 # really annoy anyone in the habit of using "--tmpdir=/tmp"
809 # really annoy anyone in the habit of using "--tmpdir=/tmp"
802 # or "--tmpdir=$HOME".
810 # or "--tmpdir=$HOME".
803 #vlog("# Removing temp dir", tmpdir)
811 #vlog("# Removing temp dir", tmpdir)
804 #shutil.rmtree(tmpdir)
812 #shutil.rmtree(tmpdir)
805 os.makedirs(tmpdir)
813 os.makedirs(tmpdir)
806 else:
814 else:
807 tmpdir = tempfile.mkdtemp('', 'hgtests.')
815 tmpdir = tempfile.mkdtemp('', 'hgtests.')
808 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
816 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
809 DAEMON_PIDS = None
817 DAEMON_PIDS = None
810 HGRCPATH = None
818 HGRCPATH = None
811
819
812 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
820 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
813 os.environ["HGMERGE"] = "internal:merge"
821 os.environ["HGMERGE"] = "internal:merge"
814 os.environ["HGUSER"] = "test"
822 os.environ["HGUSER"] = "test"
815 os.environ["HGENCODING"] = "ascii"
823 os.environ["HGENCODING"] = "ascii"
816 os.environ["HGENCODINGMODE"] = "strict"
824 os.environ["HGENCODINGMODE"] = "strict"
817 os.environ["HGPORT"] = str(options.port)
825 os.environ["HGPORT"] = str(options.port)
818 os.environ["HGPORT1"] = str(options.port + 1)
826 os.environ["HGPORT1"] = str(options.port + 1)
819 os.environ["HGPORT2"] = str(options.port + 2)
827 os.environ["HGPORT2"] = str(options.port + 2)
820
828
821 if options.with_hg:
829 if options.with_hg:
822 INST = None
830 INST = None
823 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
831 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
824
832
825 # This looks redundant with how Python initializes sys.path from
833 # This looks redundant with how Python initializes sys.path from
826 # the location of the script being executed. Needed because the
834 # the location of the script being executed. Needed because the
827 # "hg" specified by --with-hg is not the only Python script
835 # "hg" specified by --with-hg is not the only Python script
828 # executed in the test suite that needs to import 'mercurial'
836 # executed in the test suite that needs to import 'mercurial'
829 # ... which means it's not really redundant at all.
837 # ... which means it's not really redundant at all.
830 PYTHONDIR = BINDIR
838 PYTHONDIR = BINDIR
831 else:
839 else:
832 INST = os.path.join(HGTMP, "install")
840 INST = os.path.join(HGTMP, "install")
833 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
841 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
834 PYTHONDIR = os.path.join(INST, "lib", "python")
842 PYTHONDIR = os.path.join(INST, "lib", "python")
835
843
836 os.environ["BINDIR"] = BINDIR
844 os.environ["BINDIR"] = BINDIR
837 os.environ["PYTHON"] = PYTHON
845 os.environ["PYTHON"] = PYTHON
838
846
839 if not options.child:
847 if not options.child:
840 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
848 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
841 os.environ["PATH"] = os.pathsep.join(path)
849 os.environ["PATH"] = os.pathsep.join(path)
842
850
843 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
851 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
844 # can run .../tests/run-tests.py test-foo where test-foo
852 # can run .../tests/run-tests.py test-foo where test-foo
845 # adds an extension to HGRC
853 # adds an extension to HGRC
846 pypath = [PYTHONDIR, TESTDIR]
854 pypath = [PYTHONDIR, TESTDIR]
847 # We have to augment PYTHONPATH, rather than simply replacing
855 # We have to augment PYTHONPATH, rather than simply replacing
848 # it, in case external libraries are only available via current
856 # it, in case external libraries are only available via current
849 # PYTHONPATH. (In particular, the Subversion bindings on OS X
857 # PYTHONPATH. (In particular, the Subversion bindings on OS X
850 # are in /opt/subversion.)
858 # are in /opt/subversion.)
851 oldpypath = os.environ.get('PYTHONPATH')
859 oldpypath = os.environ.get('PYTHONPATH')
852 if oldpypath:
860 if oldpypath:
853 pypath.append(oldpypath)
861 pypath.append(oldpypath)
854 os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
862 os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
855
863
856 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
864 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
857
865
858 if len(args) == 0:
866 if len(args) == 0:
859 args = os.listdir(".")
867 args = os.listdir(".")
860 args.sort()
868 args.sort()
861
869
862 tests = []
870 tests = []
863 for test in args:
871 for test in args:
864 if (test.startswith("test-") and '~' not in test and
872 if (test.startswith("test-") and '~' not in test and
865 ('.' not in test or test.endswith('.py') or
873 ('.' not in test or test.endswith('.py') or
866 test.endswith('.bat'))):
874 test.endswith('.bat'))):
867 tests.append(test)
875 tests.append(test)
868 if not tests:
876 if not tests:
869 print "# Ran 0 tests, 0 skipped, 0 failed."
877 print "# Ran 0 tests, 0 skipped, 0 failed."
870 return
878 return
871
879
872 vlog("# Using TESTDIR", TESTDIR)
880 vlog("# Using TESTDIR", TESTDIR)
873 vlog("# Using HGTMP", HGTMP)
881 vlog("# Using HGTMP", HGTMP)
874 vlog("# Using PATH", os.environ["PATH"])
882 vlog("# Using PATH", os.environ["PATH"])
875 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
883 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
876
884
877 try:
885 try:
878 if len(tests) > 1 and options.jobs > 1:
886 if len(tests) > 1 and options.jobs > 1:
879 runchildren(options, tests)
887 runchildren(options, tests)
880 else:
888 else:
881 runtests(options, tests)
889 runtests(options, tests)
882 finally:
890 finally:
883 cleanup(options)
891 cleanup(options)
884
892
885 main()
893 main()
General Comments 0
You need to be logged in to leave comments. Login now