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