##// END OF EJS Templates
run-tests: add --pure flag for using pure Python modules
Martin Geisler -
r7723:a343cd25 default
parent child Browse files
Show More
@@ -1,667 +1,670 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
7 # This software may be used and distributed according to the terms
8 # of the GNU General Public License, incorporated herein by reference.
8 # of the GNU General Public License, incorporated herein by reference.
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import optparse
12 import optparse
13 import os
13 import os
14 import popen2
14 import popen2
15 import shutil
15 import shutil
16 import signal
16 import signal
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19 import time
19 import time
20
20
21 # reserved exit code to skip test (used by hghave)
21 # reserved exit code to skip test (used by hghave)
22 SKIPPED_STATUS = 80
22 SKIPPED_STATUS = 80
23 SKIPPED_PREFIX = 'skipped: '
23 SKIPPED_PREFIX = 'skipped: '
24
24
25 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
25 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
26
26
27 defaults = {
27 defaults = {
28 'jobs': ('HGTEST_JOBS', 1),
28 'jobs': ('HGTEST_JOBS', 1),
29 'timeout': ('HGTEST_TIMEOUT', 180),
29 'timeout': ('HGTEST_TIMEOUT', 180),
30 'port': ('HGTEST_PORT', 20059),
30 'port': ('HGTEST_PORT', 20059),
31 }
31 }
32
32
33 parser = optparse.OptionParser("%prog [options] [tests]")
33 parser = optparse.OptionParser("%prog [options] [tests]")
34 parser.add_option("-C", "--annotate", action="store_true",
34 parser.add_option("-C", "--annotate", action="store_true",
35 help="output files annotated with coverage")
35 help="output files annotated with coverage")
36 parser.add_option("--child", type="int",
36 parser.add_option("--child", type="int",
37 help="run as child process, summary to given fd")
37 help="run as child process, summary to given fd")
38 parser.add_option("-c", "--cover", action="store_true",
38 parser.add_option("-c", "--cover", action="store_true",
39 help="print a test coverage report")
39 help="print a test coverage report")
40 parser.add_option("-f", "--first", action="store_true",
40 parser.add_option("-f", "--first", action="store_true",
41 help="exit on the first test failure")
41 help="exit on the first test failure")
42 parser.add_option("-i", "--interactive", action="store_true",
42 parser.add_option("-i", "--interactive", action="store_true",
43 help="prompt to accept changed output")
43 help="prompt to accept changed output")
44 parser.add_option("-j", "--jobs", type="int",
44 parser.add_option("-j", "--jobs", type="int",
45 help="number of jobs to run in parallel"
45 help="number of jobs to run in parallel"
46 " (default: $%s or %d)" % defaults['jobs'])
46 " (default: $%s or %d)" % defaults['jobs'])
47 parser.add_option("--keep-tmpdir", action="store_true",
47 parser.add_option("--keep-tmpdir", action="store_true",
48 help="keep temporary directory after running tests"
48 help="keep temporary directory after running tests"
49 " (best used with --tmpdir)")
49 " (best used with --tmpdir)")
50 parser.add_option("-R", "--restart", action="store_true",
50 parser.add_option("-R", "--restart", action="store_true",
51 help="restart at last error")
51 help="restart at last error")
52 parser.add_option("-p", "--port", type="int",
52 parser.add_option("-p", "--port", type="int",
53 help="port on which servers should listen"
53 help="port on which servers should listen"
54 " (default: $%s or %d)" % defaults['port'])
54 " (default: $%s or %d)" % defaults['port'])
55 parser.add_option("-r", "--retest", action="store_true",
55 parser.add_option("-r", "--retest", action="store_true",
56 help="retest failed tests")
56 help="retest failed tests")
57 parser.add_option("-s", "--cover_stdlib", action="store_true",
57 parser.add_option("-s", "--cover_stdlib", action="store_true",
58 help="print a test coverage report inc. standard libraries")
58 help="print a test coverage report inc. standard libraries")
59 parser.add_option("-t", "--timeout", type="int",
59 parser.add_option("-t", "--timeout", type="int",
60 help="kill errant tests after TIMEOUT seconds"
60 help="kill errant tests after TIMEOUT seconds"
61 " (default: $%s or %d)" % defaults['timeout'])
61 " (default: $%s or %d)" % defaults['timeout'])
62 parser.add_option("--tmpdir", type="string",
62 parser.add_option("--tmpdir", type="string",
63 help="run tests in the given temporary directory")
63 help="run tests in the given temporary directory")
64 parser.add_option("-v", "--verbose", action="store_true",
64 parser.add_option("-v", "--verbose", action="store_true",
65 help="output verbose messages")
65 help="output verbose messages")
66 parser.add_option("-n", "--nodiff", action="store_true",
66 parser.add_option("-n", "--nodiff", action="store_true",
67 help="skip showing test changes")
67 help="skip showing test changes")
68 parser.add_option("--with-hg", type="string",
68 parser.add_option("--with-hg", type="string",
69 help="test existing install at given location")
69 help="test existing install at given location")
70 parser.add_option("--pure", action="store_true",
71 help="use pure Python code instead of C extensions")
70
72
71 for option, default in defaults.items():
73 for option, default in defaults.items():
72 defaults[option] = int(os.environ.get(*default))
74 defaults[option] = int(os.environ.get(*default))
73 parser.set_defaults(**defaults)
75 parser.set_defaults(**defaults)
74 (options, args) = parser.parse_args()
76 (options, args) = parser.parse_args()
75 verbose = options.verbose
77 verbose = options.verbose
76 nodiff = options.nodiff
78 nodiff = options.nodiff
77 coverage = options.cover or options.cover_stdlib or options.annotate
79 coverage = options.cover or options.cover_stdlib or options.annotate
78 python = sys.executable
80 python = sys.executable
79
81
80 if options.jobs < 1:
82 if options.jobs < 1:
81 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
83 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
82 sys.exit(1)
84 sys.exit(1)
83 if options.interactive and options.jobs > 1:
85 if options.interactive and options.jobs > 1:
84 print '(--interactive overrides --jobs)'
86 print '(--interactive overrides --jobs)'
85 options.jobs = 1
87 options.jobs = 1
86
88
87 def rename(src, dst):
89 def rename(src, dst):
88 """Like os.rename(), trade atomicity and opened files friendliness
90 """Like os.rename(), trade atomicity and opened files friendliness
89 for existing destination support.
91 for existing destination support.
90 """
92 """
91 shutil.copy(src, dst)
93 shutil.copy(src, dst)
92 os.remove(src)
94 os.remove(src)
93
95
94 def vlog(*msg):
96 def vlog(*msg):
95 if verbose:
97 if verbose:
96 for m in msg:
98 for m in msg:
97 print m,
99 print m,
98 print
100 print
99
101
100 def splitnewlines(text):
102 def splitnewlines(text):
101 '''like str.splitlines, but only split on newlines.
103 '''like str.splitlines, but only split on newlines.
102 keep line endings.'''
104 keep line endings.'''
103 i = 0
105 i = 0
104 lines = []
106 lines = []
105 while True:
107 while True:
106 n = text.find('\n', i)
108 n = text.find('\n', i)
107 if n == -1:
109 if n == -1:
108 last = text[i:]
110 last = text[i:]
109 if last:
111 if last:
110 lines.append(last)
112 lines.append(last)
111 return lines
113 return lines
112 lines.append(text[i:n+1])
114 lines.append(text[i:n+1])
113 i = n + 1
115 i = n + 1
114
116
115 def extract_missing_features(lines):
117 def extract_missing_features(lines):
116 '''Extract missing/unknown features log lines as a list'''
118 '''Extract missing/unknown features log lines as a list'''
117 missing = []
119 missing = []
118 for line in lines:
120 for line in lines:
119 if not line.startswith(SKIPPED_PREFIX):
121 if not line.startswith(SKIPPED_PREFIX):
120 continue
122 continue
121 line = line.splitlines()[0]
123 line = line.splitlines()[0]
122 missing.append(line[len(SKIPPED_PREFIX):])
124 missing.append(line[len(SKIPPED_PREFIX):])
123
125
124 return missing
126 return missing
125
127
126 def show_diff(expected, output):
128 def show_diff(expected, output):
127 for line in difflib.unified_diff(expected, output,
129 for line in difflib.unified_diff(expected, output,
128 "Expected output", "Test output"):
130 "Expected output", "Test output"):
129 sys.stdout.write(line)
131 sys.stdout.write(line)
130
132
131 def find_program(program):
133 def find_program(program):
132 """Search PATH for a executable program"""
134 """Search PATH for a executable program"""
133 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
135 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
134 name = os.path.join(p, program)
136 name = os.path.join(p, program)
135 if os.access(name, os.X_OK):
137 if os.access(name, os.X_OK):
136 return name
138 return name
137 return None
139 return None
138
140
139 def check_required_tools():
141 def check_required_tools():
140 # Before we go any further, check for pre-requisite tools
142 # Before we go any further, check for pre-requisite tools
141 # stuff from coreutils (cat, rm, etc) are not tested
143 # stuff from coreutils (cat, rm, etc) are not tested
142 for p in required_tools:
144 for p in required_tools:
143 if os.name == 'nt':
145 if os.name == 'nt':
144 p += '.exe'
146 p += '.exe'
145 found = find_program(p)
147 found = find_program(p)
146 if found:
148 if found:
147 vlog("# Found prerequisite", p, "at", found)
149 vlog("# Found prerequisite", p, "at", found)
148 else:
150 else:
149 print "WARNING: Did not find prerequisite tool: "+p
151 print "WARNING: Did not find prerequisite tool: "+p
150
152
151 def cleanup_exit():
153 def cleanup_exit():
152 if not options.keep_tmpdir:
154 if not options.keep_tmpdir:
153 if verbose:
155 if verbose:
154 print "# Cleaning up HGTMP", HGTMP
156 print "# Cleaning up HGTMP", HGTMP
155 shutil.rmtree(HGTMP, True)
157 shutil.rmtree(HGTMP, True)
156
158
157 def use_correct_python():
159 def use_correct_python():
158 # some tests run python interpreter. they must use same
160 # some tests run python interpreter. they must use same
159 # interpreter we use or bad things will happen.
161 # interpreter we use or bad things will happen.
160 exedir, exename = os.path.split(sys.executable)
162 exedir, exename = os.path.split(sys.executable)
161 if exename == 'python':
163 if exename == 'python':
162 path = find_program('python')
164 path = find_program('python')
163 if os.path.dirname(path) == exedir:
165 if os.path.dirname(path) == exedir:
164 return
166 return
165 vlog('# Making python executable in test path use correct Python')
167 vlog('# Making python executable in test path use correct Python')
166 my_python = os.path.join(BINDIR, 'python')
168 my_python = os.path.join(BINDIR, 'python')
167 try:
169 try:
168 os.symlink(sys.executable, my_python)
170 os.symlink(sys.executable, my_python)
169 except AttributeError:
171 except AttributeError:
170 # windows fallback
172 # windows fallback
171 shutil.copyfile(sys.executable, my_python)
173 shutil.copyfile(sys.executable, my_python)
172 shutil.copymode(sys.executable, my_python)
174 shutil.copymode(sys.executable, my_python)
173
175
174 def install_hg():
176 def install_hg():
175 global python
177 global python
176 vlog("# Performing temporary installation of HG")
178 vlog("# Performing temporary installation of HG")
177 installerrs = os.path.join("tests", "install.err")
179 installerrs = os.path.join("tests", "install.err")
180 pure = options.pure and "--pure" or ""
178
181
179 # Run installer in hg root
182 # Run installer in hg root
180 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
183 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
181 cmd = ('%s setup.py clean --all'
184 cmd = ('%s setup.py %s clean --all'
182 ' install --force --prefix="%s" --install-lib="%s"'
185 ' install --force --prefix="%s" --install-lib="%s"'
183 ' --install-scripts="%s" >%s 2>&1'
186 ' --install-scripts="%s" >%s 2>&1'
184 % (sys.executable, INST, PYTHONDIR, BINDIR, installerrs))
187 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
185 vlog("# Running", cmd)
188 vlog("# Running", cmd)
186 if os.system(cmd) == 0:
189 if os.system(cmd) == 0:
187 if not verbose:
190 if not verbose:
188 os.remove(installerrs)
191 os.remove(installerrs)
189 else:
192 else:
190 f = open(installerrs)
193 f = open(installerrs)
191 for line in f:
194 for line in f:
192 print line,
195 print line,
193 f.close()
196 f.close()
194 sys.exit(1)
197 sys.exit(1)
195 os.chdir(TESTDIR)
198 os.chdir(TESTDIR)
196
199
197 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
200 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
198
201
199 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
202 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
200 pythonpath = os.environ.get("PYTHONPATH")
203 pythonpath = os.environ.get("PYTHONPATH")
201 if pythonpath:
204 if pythonpath:
202 pythonpath = pydir + os.pathsep + pythonpath
205 pythonpath = pydir + os.pathsep + pythonpath
203 else:
206 else:
204 pythonpath = pydir
207 pythonpath = pydir
205 os.environ["PYTHONPATH"] = pythonpath
208 os.environ["PYTHONPATH"] = pythonpath
206
209
207 use_correct_python()
210 use_correct_python()
208 global hgpkg
211 global hgpkg
209 hgpkg = _hgpath()
212 hgpkg = _hgpath()
210
213
211 vlog("# Installing dummy diffstat")
214 vlog("# Installing dummy diffstat")
212 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
215 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
213 f.write('#!' + sys.executable + '\n'
216 f.write('#!' + sys.executable + '\n'
214 'import sys\n'
217 'import sys\n'
215 'files = 0\n'
218 'files = 0\n'
216 'for line in sys.stdin:\n'
219 'for line in sys.stdin:\n'
217 ' if line.startswith("diff "):\n'
220 ' if line.startswith("diff "):\n'
218 ' files += 1\n'
221 ' files += 1\n'
219 'sys.stdout.write("files patched: %d\\n" % files)\n')
222 'sys.stdout.write("files patched: %d\\n" % files)\n')
220 f.close()
223 f.close()
221 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
224 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
222
225
223 if coverage:
226 if coverage:
224 vlog("# Installing coverage wrapper")
227 vlog("# Installing coverage wrapper")
225 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
228 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
226 if os.path.exists(COVERAGE_FILE):
229 if os.path.exists(COVERAGE_FILE):
227 os.unlink(COVERAGE_FILE)
230 os.unlink(COVERAGE_FILE)
228 # Create a wrapper script to invoke hg via coverage.py
231 # Create a wrapper script to invoke hg via coverage.py
229 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
232 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
230 f = open(os.path.join(BINDIR, 'hg'), 'w')
233 f = open(os.path.join(BINDIR, 'hg'), 'w')
231 f.write('#!' + sys.executable + '\n')
234 f.write('#!' + sys.executable + '\n')
232 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
235 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
233 '"%s", "-x", "%s"] + sys.argv[1:])\n' %
236 '"%s", "-x", "%s"] + sys.argv[1:])\n' %
234 (os.path.join(TESTDIR, 'coverage.py'),
237 (os.path.join(TESTDIR, 'coverage.py'),
235 os.path.join(BINDIR, '_hg.py')))
238 os.path.join(BINDIR, '_hg.py')))
236 f.close()
239 f.close()
237 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
240 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
238 python = '"%s" "%s" -x' % (sys.executable,
241 python = '"%s" "%s" -x' % (sys.executable,
239 os.path.join(TESTDIR,'coverage.py'))
242 os.path.join(TESTDIR,'coverage.py'))
240
243
241 def output_coverage():
244 def output_coverage():
242 vlog("# Producing coverage report")
245 vlog("# Producing coverage report")
243 omit = [BINDIR, TESTDIR, PYTHONDIR]
246 omit = [BINDIR, TESTDIR, PYTHONDIR]
244 if not options.cover_stdlib:
247 if not options.cover_stdlib:
245 # Exclude as system paths (ignoring empty strings seen on win)
248 # Exclude as system paths (ignoring empty strings seen on win)
246 omit += [x for x in sys.path if x != '']
249 omit += [x for x in sys.path if x != '']
247 omit = ','.join(omit)
250 omit = ','.join(omit)
248 os.chdir(PYTHONDIR)
251 os.chdir(PYTHONDIR)
249 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
252 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
250 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
253 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
251 vlog("# Running: "+cmd)
254 vlog("# Running: "+cmd)
252 os.system(cmd)
255 os.system(cmd)
253 if options.annotate:
256 if options.annotate:
254 adir = os.path.join(TESTDIR, 'annotated')
257 adir = os.path.join(TESTDIR, 'annotated')
255 if not os.path.isdir(adir):
258 if not os.path.isdir(adir):
256 os.mkdir(adir)
259 os.mkdir(adir)
257 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
260 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
258 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
261 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
259 adir, omit)
262 adir, omit)
260 vlog("# Running: "+cmd)
263 vlog("# Running: "+cmd)
261 os.system(cmd)
264 os.system(cmd)
262
265
263 class Timeout(Exception):
266 class Timeout(Exception):
264 pass
267 pass
265
268
266 def alarmed(signum, frame):
269 def alarmed(signum, frame):
267 raise Timeout
270 raise Timeout
268
271
269 def run(cmd):
272 def run(cmd):
270 """Run command in a sub-process, capturing the output (stdout and stderr).
273 """Run command in a sub-process, capturing the output (stdout and stderr).
271 Return the exist code, and output."""
274 Return the exist code, and output."""
272 # TODO: Use subprocess.Popen if we're running on Python 2.4
275 # TODO: Use subprocess.Popen if we're running on Python 2.4
273 if os.name == 'nt':
276 if os.name == 'nt':
274 tochild, fromchild = os.popen4(cmd)
277 tochild, fromchild = os.popen4(cmd)
275 tochild.close()
278 tochild.close()
276 output = fromchild.read()
279 output = fromchild.read()
277 ret = fromchild.close()
280 ret = fromchild.close()
278 if ret == None:
281 if ret == None:
279 ret = 0
282 ret = 0
280 else:
283 else:
281 proc = popen2.Popen4(cmd)
284 proc = popen2.Popen4(cmd)
282 try:
285 try:
283 output = ''
286 output = ''
284 proc.tochild.close()
287 proc.tochild.close()
285 output = proc.fromchild.read()
288 output = proc.fromchild.read()
286 ret = proc.wait()
289 ret = proc.wait()
287 if os.WIFEXITED(ret):
290 if os.WIFEXITED(ret):
288 ret = os.WEXITSTATUS(ret)
291 ret = os.WEXITSTATUS(ret)
289 except Timeout:
292 except Timeout:
290 vlog('# Process %d timed out - killing it' % proc.pid)
293 vlog('# Process %d timed out - killing it' % proc.pid)
291 os.kill(proc.pid, signal.SIGTERM)
294 os.kill(proc.pid, signal.SIGTERM)
292 ret = proc.wait()
295 ret = proc.wait()
293 if ret == 0:
296 if ret == 0:
294 ret = signal.SIGTERM << 8
297 ret = signal.SIGTERM << 8
295 output += ("\n### Abort: timeout after %d seconds.\n"
298 output += ("\n### Abort: timeout after %d seconds.\n"
296 % options.timeout)
299 % options.timeout)
297 return ret, splitnewlines(output)
300 return ret, splitnewlines(output)
298
301
299 def run_one(test, skips, fails):
302 def run_one(test, skips, fails):
300 '''tristate output:
303 '''tristate output:
301 None -> skipped
304 None -> skipped
302 True -> passed
305 True -> passed
303 False -> failed'''
306 False -> failed'''
304
307
305 def skip(msg):
308 def skip(msg):
306 if not verbose:
309 if not verbose:
307 skips.append((test, msg))
310 skips.append((test, msg))
308 else:
311 else:
309 print "\nSkipping %s: %s" % (test, msg)
312 print "\nSkipping %s: %s" % (test, msg)
310 return None
313 return None
311
314
312 def fail(msg):
315 def fail(msg):
313 fails.append((test, msg))
316 fails.append((test, msg))
314 if not nodiff:
317 if not nodiff:
315 print "\nERROR: %s %s" % (test, msg)
318 print "\nERROR: %s %s" % (test, msg)
316 return None
319 return None
317
320
318 vlog("# Test", test)
321 vlog("# Test", test)
319
322
320 # create a fresh hgrc
323 # create a fresh hgrc
321 hgrc = file(HGRCPATH, 'w+')
324 hgrc = file(HGRCPATH, 'w+')
322 hgrc.write('[ui]\n')
325 hgrc.write('[ui]\n')
323 hgrc.write('slash = True\n')
326 hgrc.write('slash = True\n')
324 hgrc.write('[defaults]\n')
327 hgrc.write('[defaults]\n')
325 hgrc.write('backout = -d "0 0"\n')
328 hgrc.write('backout = -d "0 0"\n')
326 hgrc.write('commit = -d "0 0"\n')
329 hgrc.write('commit = -d "0 0"\n')
327 hgrc.write('debugrawcommit = -d "0 0"\n')
330 hgrc.write('debugrawcommit = -d "0 0"\n')
328 hgrc.write('tag = -d "0 0"\n')
331 hgrc.write('tag = -d "0 0"\n')
329 hgrc.close()
332 hgrc.close()
330
333
331 err = os.path.join(TESTDIR, test+".err")
334 err = os.path.join(TESTDIR, test+".err")
332 ref = os.path.join(TESTDIR, test+".out")
335 ref = os.path.join(TESTDIR, test+".out")
333 testpath = os.path.join(TESTDIR, test)
336 testpath = os.path.join(TESTDIR, test)
334
337
335 if os.path.exists(err):
338 if os.path.exists(err):
336 os.remove(err) # Remove any previous output files
339 os.remove(err) # Remove any previous output files
337
340
338 # Make a tmp subdirectory to work in
341 # Make a tmp subdirectory to work in
339 tmpd = os.path.join(HGTMP, test)
342 tmpd = os.path.join(HGTMP, test)
340 os.mkdir(tmpd)
343 os.mkdir(tmpd)
341 os.chdir(tmpd)
344 os.chdir(tmpd)
342
345
343 try:
346 try:
344 tf = open(testpath)
347 tf = open(testpath)
345 firstline = tf.readline().rstrip()
348 firstline = tf.readline().rstrip()
346 tf.close()
349 tf.close()
347 except:
350 except:
348 firstline = ''
351 firstline = ''
349 lctest = test.lower()
352 lctest = test.lower()
350
353
351 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
354 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
352 cmd = '%s "%s"' % (python, testpath)
355 cmd = '%s "%s"' % (python, testpath)
353 elif lctest.endswith('.bat'):
356 elif lctest.endswith('.bat'):
354 # do not run batch scripts on non-windows
357 # do not run batch scripts on non-windows
355 if os.name != 'nt':
358 if os.name != 'nt':
356 return skip("batch script")
359 return skip("batch script")
357 # To reliably get the error code from batch files on WinXP,
360 # To reliably get the error code from batch files on WinXP,
358 # the "cmd /c call" prefix is needed. Grrr
361 # the "cmd /c call" prefix is needed. Grrr
359 cmd = 'cmd /c call "%s"' % testpath
362 cmd = 'cmd /c call "%s"' % testpath
360 else:
363 else:
361 # do not run shell scripts on windows
364 # do not run shell scripts on windows
362 if os.name == 'nt':
365 if os.name == 'nt':
363 return skip("shell script")
366 return skip("shell script")
364 # do not try to run non-executable programs
367 # do not try to run non-executable programs
365 if not os.path.exists(testpath):
368 if not os.path.exists(testpath):
366 return fail("does not exist")
369 return fail("does not exist")
367 elif not os.access(testpath, os.X_OK):
370 elif not os.access(testpath, os.X_OK):
368 return skip("not executable")
371 return skip("not executable")
369 cmd = '"%s"' % testpath
372 cmd = '"%s"' % testpath
370
373
371 if options.timeout > 0:
374 if options.timeout > 0:
372 signal.alarm(options.timeout)
375 signal.alarm(options.timeout)
373
376
374 vlog("# Running", cmd)
377 vlog("# Running", cmd)
375 ret, out = run(cmd)
378 ret, out = run(cmd)
376 vlog("# Ret was:", ret)
379 vlog("# Ret was:", ret)
377
380
378 if options.timeout > 0:
381 if options.timeout > 0:
379 signal.alarm(0)
382 signal.alarm(0)
380
383
381 mark = '.'
384 mark = '.'
382
385
383 skipped = (ret == SKIPPED_STATUS)
386 skipped = (ret == SKIPPED_STATUS)
384 # If reference output file exists, check test output against it
387 # If reference output file exists, check test output against it
385 if os.path.exists(ref):
388 if os.path.exists(ref):
386 f = open(ref, "r")
389 f = open(ref, "r")
387 ref_out = splitnewlines(f.read())
390 ref_out = splitnewlines(f.read())
388 f.close()
391 f.close()
389 else:
392 else:
390 ref_out = []
393 ref_out = []
391 if skipped:
394 if skipped:
392 mark = 's'
395 mark = 's'
393 missing = extract_missing_features(out)
396 missing = extract_missing_features(out)
394 if not missing:
397 if not missing:
395 missing = ['irrelevant']
398 missing = ['irrelevant']
396 skip(missing[-1])
399 skip(missing[-1])
397 elif out != ref_out:
400 elif out != ref_out:
398 mark = '!'
401 mark = '!'
399 if ret:
402 if ret:
400 fail("output changed and returned error code %d" % ret)
403 fail("output changed and returned error code %d" % ret)
401 else:
404 else:
402 fail("output changed")
405 fail("output changed")
403 if not nodiff:
406 if not nodiff:
404 show_diff(ref_out, out)
407 show_diff(ref_out, out)
405 ret = 1
408 ret = 1
406 elif ret:
409 elif ret:
407 mark = '!'
410 mark = '!'
408 fail("returned error code %d" % ret)
411 fail("returned error code %d" % ret)
409
412
410 if not verbose:
413 if not verbose:
411 sys.stdout.write(mark)
414 sys.stdout.write(mark)
412 sys.stdout.flush()
415 sys.stdout.flush()
413
416
414 if ret != 0 and not skipped:
417 if ret != 0 and not skipped:
415 # Save errors to a file for diagnosis
418 # Save errors to a file for diagnosis
416 f = open(err, "wb")
419 f = open(err, "wb")
417 for line in out:
420 for line in out:
418 f.write(line)
421 f.write(line)
419 f.close()
422 f.close()
420
423
421 # Kill off any leftover daemon processes
424 # Kill off any leftover daemon processes
422 try:
425 try:
423 fp = file(DAEMON_PIDS)
426 fp = file(DAEMON_PIDS)
424 for line in fp:
427 for line in fp:
425 try:
428 try:
426 pid = int(line)
429 pid = int(line)
427 except ValueError:
430 except ValueError:
428 continue
431 continue
429 try:
432 try:
430 os.kill(pid, 0)
433 os.kill(pid, 0)
431 vlog('# Killing daemon process %d' % pid)
434 vlog('# Killing daemon process %d' % pid)
432 os.kill(pid, signal.SIGTERM)
435 os.kill(pid, signal.SIGTERM)
433 time.sleep(0.25)
436 time.sleep(0.25)
434 os.kill(pid, 0)
437 os.kill(pid, 0)
435 vlog('# Daemon process %d is stuck - really killing it' % pid)
438 vlog('# Daemon process %d is stuck - really killing it' % pid)
436 os.kill(pid, signal.SIGKILL)
439 os.kill(pid, signal.SIGKILL)
437 except OSError, err:
440 except OSError, err:
438 if err.errno != errno.ESRCH:
441 if err.errno != errno.ESRCH:
439 raise
442 raise
440 fp.close()
443 fp.close()
441 os.unlink(DAEMON_PIDS)
444 os.unlink(DAEMON_PIDS)
442 except IOError:
445 except IOError:
443 pass
446 pass
444
447
445 os.chdir(TESTDIR)
448 os.chdir(TESTDIR)
446 if not options.keep_tmpdir:
449 if not options.keep_tmpdir:
447 shutil.rmtree(tmpd, True)
450 shutil.rmtree(tmpd, True)
448 if skipped:
451 if skipped:
449 return None
452 return None
450 return ret == 0
453 return ret == 0
451
454
452 if not options.child:
455 if not options.child:
453 os.umask(022)
456 os.umask(022)
454
457
455 check_required_tools()
458 check_required_tools()
456
459
457 # Reset some environment variables to well-known values so that
460 # Reset some environment variables to well-known values so that
458 # the tests produce repeatable output.
461 # the tests produce repeatable output.
459 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
462 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
460 os.environ['TZ'] = 'GMT'
463 os.environ['TZ'] = 'GMT'
461 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
464 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
462 os.environ['CDPATH'] = ''
465 os.environ['CDPATH'] = ''
463
466
464 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
467 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
465 HGTMP = os.environ['HGTMP'] = tempfile.mkdtemp('', 'hgtests.', options.tmpdir)
468 HGTMP = os.environ['HGTMP'] = tempfile.mkdtemp('', 'hgtests.', options.tmpdir)
466 DAEMON_PIDS = None
469 DAEMON_PIDS = None
467 HGRCPATH = None
470 HGRCPATH = None
468
471
469 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
472 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
470 os.environ["HGMERGE"] = "internal:merge"
473 os.environ["HGMERGE"] = "internal:merge"
471 os.environ["HGUSER"] = "test"
474 os.environ["HGUSER"] = "test"
472 os.environ["HGENCODING"] = "ascii"
475 os.environ["HGENCODING"] = "ascii"
473 os.environ["HGENCODINGMODE"] = "strict"
476 os.environ["HGENCODINGMODE"] = "strict"
474 os.environ["HGPORT"] = str(options.port)
477 os.environ["HGPORT"] = str(options.port)
475 os.environ["HGPORT1"] = str(options.port + 1)
478 os.environ["HGPORT1"] = str(options.port + 1)
476 os.environ["HGPORT2"] = str(options.port + 2)
479 os.environ["HGPORT2"] = str(options.port + 2)
477
480
478 if options.with_hg:
481 if options.with_hg:
479 INST = options.with_hg
482 INST = options.with_hg
480 else:
483 else:
481 INST = os.path.join(HGTMP, "install")
484 INST = os.path.join(HGTMP, "install")
482 BINDIR = os.path.join(INST, "bin")
485 BINDIR = os.path.join(INST, "bin")
483 PYTHONDIR = os.path.join(INST, "lib", "python")
486 PYTHONDIR = os.path.join(INST, "lib", "python")
484 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
487 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
485
488
486 def _hgpath():
489 def _hgpath():
487 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
490 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
488 hgpath = os.popen(cmd % python)
491 hgpath = os.popen(cmd % python)
489 path = hgpath.read().strip()
492 path = hgpath.read().strip()
490 hgpath.close()
493 hgpath.close()
491 return path
494 return path
492
495
493 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
496 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
494 hgpkg = None
497 hgpkg = None
495
498
496 def run_children(tests):
499 def run_children(tests):
497 if not options.with_hg:
500 if not options.with_hg:
498 install_hg()
501 install_hg()
499 if hgpkg != expecthg:
502 if hgpkg != expecthg:
500 print '# Testing unexpected mercurial: %s' % hgpkg
503 print '# Testing unexpected mercurial: %s' % hgpkg
501
504
502 optcopy = dict(options.__dict__)
505 optcopy = dict(options.__dict__)
503 optcopy['jobs'] = 1
506 optcopy['jobs'] = 1
504 optcopy['with_hg'] = INST
507 optcopy['with_hg'] = INST
505 opts = []
508 opts = []
506 for opt, value in optcopy.iteritems():
509 for opt, value in optcopy.iteritems():
507 name = '--' + opt.replace('_', '-')
510 name = '--' + opt.replace('_', '-')
508 if value is True:
511 if value is True:
509 opts.append(name)
512 opts.append(name)
510 elif value is not None:
513 elif value is not None:
511 opts.append(name + '=' + str(value))
514 opts.append(name + '=' + str(value))
512
515
513 tests.reverse()
516 tests.reverse()
514 jobs = [[] for j in xrange(options.jobs)]
517 jobs = [[] for j in xrange(options.jobs)]
515 while tests:
518 while tests:
516 for j in xrange(options.jobs):
519 for j in xrange(options.jobs):
517 if not tests: break
520 if not tests: break
518 jobs[j].append(tests.pop())
521 jobs[j].append(tests.pop())
519 fps = {}
522 fps = {}
520 for j in xrange(len(jobs)):
523 for j in xrange(len(jobs)):
521 job = jobs[j]
524 job = jobs[j]
522 if not job:
525 if not job:
523 continue
526 continue
524 rfd, wfd = os.pipe()
527 rfd, wfd = os.pipe()
525 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
528 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
526 cmdline = [python, sys.argv[0]] + opts + childopts + job
529 cmdline = [python, sys.argv[0]] + opts + childopts + job
527 vlog(' '.join(cmdline))
530 vlog(' '.join(cmdline))
528 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
531 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
529 os.close(wfd)
532 os.close(wfd)
530 failures = 0
533 failures = 0
531 tested, skipped, failed = 0, 0, 0
534 tested, skipped, failed = 0, 0, 0
532 skips = []
535 skips = []
533 fails = []
536 fails = []
534 while fps:
537 while fps:
535 pid, status = os.wait()
538 pid, status = os.wait()
536 fp = fps.pop(pid)
539 fp = fps.pop(pid)
537 l = fp.read().splitlines()
540 l = fp.read().splitlines()
538 test, skip, fail = map(int, l[:3])
541 test, skip, fail = map(int, l[:3])
539 split = -fail or len(l)
542 split = -fail or len(l)
540 for s in l[3:split]:
543 for s in l[3:split]:
541 skips.append(s.split(" ", 1))
544 skips.append(s.split(" ", 1))
542 for s in l[split:]:
545 for s in l[split:]:
543 fails.append(s.split(" ", 1))
546 fails.append(s.split(" ", 1))
544 tested += test
547 tested += test
545 skipped += skip
548 skipped += skip
546 failed += fail
549 failed += fail
547 vlog('pid %d exited, status %d' % (pid, status))
550 vlog('pid %d exited, status %d' % (pid, status))
548 failures |= status
551 failures |= status
549 print
552 print
550 for s in skips:
553 for s in skips:
551 print "Skipped %s: %s" % (s[0], s[1])
554 print "Skipped %s: %s" % (s[0], s[1])
552 for s in fails:
555 for s in fails:
553 print "Failed %s: %s" % (s[0], s[1])
556 print "Failed %s: %s" % (s[0], s[1])
554
557
555 if hgpkg != expecthg:
558 if hgpkg != expecthg:
556 print '# Tested unexpected mercurial: %s' % hgpkg
559 print '# Tested unexpected mercurial: %s' % hgpkg
557 print "# Ran %d tests, %d skipped, %d failed." % (
560 print "# Ran %d tests, %d skipped, %d failed." % (
558 tested, skipped, failed)
561 tested, skipped, failed)
559 sys.exit(failures != 0)
562 sys.exit(failures != 0)
560
563
561 def run_tests(tests):
564 def run_tests(tests):
562 global DAEMON_PIDS, HGRCPATH
565 global DAEMON_PIDS, HGRCPATH
563 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
566 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
564 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
567 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
565
568
566 try:
569 try:
567 if not options.with_hg:
570 if not options.with_hg:
568 install_hg()
571 install_hg()
569
572
570 if hgpkg != expecthg:
573 if hgpkg != expecthg:
571 print '# Testing unexpected mercurial: %s' % hgpkg
574 print '# Testing unexpected mercurial: %s' % hgpkg
572
575
573 if options.timeout > 0:
576 if options.timeout > 0:
574 try:
577 try:
575 signal.signal(signal.SIGALRM, alarmed)
578 signal.signal(signal.SIGALRM, alarmed)
576 vlog('# Running tests with %d-second timeout' %
579 vlog('# Running tests with %d-second timeout' %
577 options.timeout)
580 options.timeout)
578 except AttributeError:
581 except AttributeError:
579 print 'WARNING: cannot run tests with timeouts'
582 print 'WARNING: cannot run tests with timeouts'
580 options.timeout = 0
583 options.timeout = 0
581
584
582 tested = 0
585 tested = 0
583 failed = 0
586 failed = 0
584 skipped = 0
587 skipped = 0
585
588
586 if options.restart:
589 if options.restart:
587 orig = list(tests)
590 orig = list(tests)
588 while tests:
591 while tests:
589 if os.path.exists(tests[0] + ".err"):
592 if os.path.exists(tests[0] + ".err"):
590 break
593 break
591 tests.pop(0)
594 tests.pop(0)
592 if not tests:
595 if not tests:
593 print "running all tests"
596 print "running all tests"
594 tests = orig
597 tests = orig
595
598
596 skips = []
599 skips = []
597 fails = []
600 fails = []
598 for test in tests:
601 for test in tests:
599 if options.retest and not os.path.exists(test + ".err"):
602 if options.retest and not os.path.exists(test + ".err"):
600 skipped += 1
603 skipped += 1
601 continue
604 continue
602 ret = run_one(test, skips, fails)
605 ret = run_one(test, skips, fails)
603 if ret is None:
606 if ret is None:
604 skipped += 1
607 skipped += 1
605 elif not ret:
608 elif not ret:
606 if options.interactive:
609 if options.interactive:
607 print "Accept this change? [n] ",
610 print "Accept this change? [n] ",
608 answer = sys.stdin.readline().strip()
611 answer = sys.stdin.readline().strip()
609 if answer.lower() in "y yes".split():
612 if answer.lower() in "y yes".split():
610 rename(test + ".err", test + ".out")
613 rename(test + ".err", test + ".out")
611 tested += 1
614 tested += 1
612 fails.pop()
615 fails.pop()
613 continue
616 continue
614 failed += 1
617 failed += 1
615 if options.first:
618 if options.first:
616 break
619 break
617 tested += 1
620 tested += 1
618
621
619 if options.child:
622 if options.child:
620 fp = os.fdopen(options.child, 'w')
623 fp = os.fdopen(options.child, 'w')
621 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
624 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
622 for s in skips:
625 for s in skips:
623 fp.write("%s %s\n" % s)
626 fp.write("%s %s\n" % s)
624 for s in fails:
627 for s in fails:
625 fp.write("%s %s\n" % s)
628 fp.write("%s %s\n" % s)
626 fp.close()
629 fp.close()
627 else:
630 else:
628 print
631 print
629 for s in skips:
632 for s in skips:
630 print "Skipped %s: %s" % s
633 print "Skipped %s: %s" % s
631 for s in fails:
634 for s in fails:
632 print "Failed %s: %s" % s
635 print "Failed %s: %s" % s
633 if hgpkg != expecthg:
636 if hgpkg != expecthg:
634 print '# Tested unexpected mercurial: %s' % hgpkg
637 print '# Tested unexpected mercurial: %s' % hgpkg
635 print "# Ran %d tests, %d skipped, %d failed." % (
638 print "# Ran %d tests, %d skipped, %d failed." % (
636 tested, skipped, failed)
639 tested, skipped, failed)
637
640
638 if coverage:
641 if coverage:
639 output_coverage()
642 output_coverage()
640 except KeyboardInterrupt:
643 except KeyboardInterrupt:
641 failed = True
644 failed = True
642 print "\ninterrupted!"
645 print "\ninterrupted!"
643
646
644 if failed:
647 if failed:
645 sys.exit(1)
648 sys.exit(1)
646
649
647 if len(args) == 0:
650 if len(args) == 0:
648 args = os.listdir(".")
651 args = os.listdir(".")
649 args.sort()
652 args.sort()
650
653
651 tests = []
654 tests = []
652 for test in args:
655 for test in args:
653 if (test.startswith("test-") and '~' not in test and
656 if (test.startswith("test-") and '~' not in test and
654 ('.' not in test or test.endswith('.py') or
657 ('.' not in test or test.endswith('.py') or
655 test.endswith('.bat'))):
658 test.endswith('.bat'))):
656 tests.append(test)
659 tests.append(test)
657
660
658 vlog("# Using TESTDIR", TESTDIR)
661 vlog("# Using TESTDIR", TESTDIR)
659 vlog("# Using HGTMP", HGTMP)
662 vlog("# Using HGTMP", HGTMP)
660
663
661 try:
664 try:
662 if len(tests) > 1 and options.jobs > 1:
665 if len(tests) > 1 and options.jobs > 1:
663 run_children(tests)
666 run_children(tests)
664 else:
667 else:
665 run_tests(tests)
668 run_tests(tests)
666 finally:
669 finally:
667 cleanup_exit()
670 cleanup_exit()
General Comments 0
You need to be logged in to leave comments. Login now