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