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