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