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