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