##// END OF EJS Templates
run-tests.py must print changed test output no matter what exit code is.
Vadim Gelfer -
r2213:6f76a479 default
parent child Browse files
Show More
@@ -1,251 +1,253 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 os, sys, shutil, re
10 import os, sys, shutil, re
11 import tempfile
11 import tempfile
12 import difflib
12 import difflib
13 import popen2
13 import popen2
14 from optparse import OptionParser
14 from optparse import OptionParser
15
15
16 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
16 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
17
17
18 parser = OptionParser("%prog [options] [tests]")
18 parser = OptionParser("%prog [options] [tests]")
19 parser.add_option("-v", "--verbose", action="store_true",
19 parser.add_option("-v", "--verbose", action="store_true",
20 help="output verbose messages")
20 help="output verbose messages")
21 parser.add_option("-c", "--cover", action="store_true",
21 parser.add_option("-c", "--cover", action="store_true",
22 help="print a test coverage report")
22 help="print a test coverage report")
23 parser.add_option("-s", "--cover_stdlib", action="store_true",
23 parser.add_option("-s", "--cover_stdlib", action="store_true",
24 help="print a test coverage report inc. standard libraries")
24 help="print a test coverage report inc. standard libraries")
25 parser.add_option("-C", "--annotate", action="store_true",
25 parser.add_option("-C", "--annotate", action="store_true",
26 help="output files annotated with coverage")
26 help="output files annotated with coverage")
27 (options, args) = parser.parse_args()
27 (options, args) = parser.parse_args()
28 verbose = options.verbose
28 verbose = options.verbose
29 coverage = options.cover or options.cover_stdlib or options.annotate
29 coverage = options.cover or options.cover_stdlib or options.annotate
30
30
31 def vlog(*msg):
31 def vlog(*msg):
32 if verbose:
32 if verbose:
33 for m in msg:
33 for m in msg:
34 print m,
34 print m,
35 print
35 print
36
36
37 def show_diff(expected, output):
37 def show_diff(expected, output):
38 for line in difflib.unified_diff(expected, output,
38 for line in difflib.unified_diff(expected, output,
39 "Expected output", "Test output", lineterm=''):
39 "Expected output", "Test output", lineterm=''):
40 print line
40 print line
41
41
42 def find_program(program):
42 def find_program(program):
43 """Search PATH for a executable program"""
43 """Search PATH for a executable program"""
44 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
44 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
45 name = os.path.join(p, program)
45 name = os.path.join(p, program)
46 if os.access(name, os.X_OK):
46 if os.access(name, os.X_OK):
47 return name
47 return name
48 return None
48 return None
49
49
50 def check_required_tools():
50 def check_required_tools():
51 # Before we go any further, check for pre-requisite tools
51 # Before we go any further, check for pre-requisite tools
52 # stuff from coreutils (cat, rm, etc) are not tested
52 # stuff from coreutils (cat, rm, etc) are not tested
53 for p in required_tools:
53 for p in required_tools:
54 if os.name == 'nt':
54 if os.name == 'nt':
55 p += '.exe'
55 p += '.exe'
56 found = find_program(p)
56 found = find_program(p)
57 if found:
57 if found:
58 vlog("# Found prerequisite", p, "at", found)
58 vlog("# Found prerequisite", p, "at", found)
59 else:
59 else:
60 print "WARNING: Did not find prerequisite tool: "+p
60 print "WARNING: Did not find prerequisite tool: "+p
61
61
62 def cleanup_exit():
62 def cleanup_exit():
63 if verbose:
63 if verbose:
64 print "# Cleaning up HGTMP", HGTMP
64 print "# Cleaning up HGTMP", HGTMP
65 shutil.rmtree(HGTMP, True)
65 shutil.rmtree(HGTMP, True)
66
66
67 def install_hg():
67 def install_hg():
68 vlog("# Performing temporary installation of HG")
68 vlog("# Performing temporary installation of HG")
69 installerrs = os.path.join("tests", "install.err")
69 installerrs = os.path.join("tests", "install.err")
70
70
71 os.chdir("..") # Get back to hg root
71 os.chdir("..") # Get back to hg root
72 cmd = ('%s setup.py clean --all'
72 cmd = ('%s setup.py clean --all'
73 ' install --force --home="%s" --install-lib="%s" >%s 2>&1'
73 ' install --force --home="%s" --install-lib="%s" >%s 2>&1'
74 % (sys.executable, INST, PYTHONDIR, installerrs))
74 % (sys.executable, INST, PYTHONDIR, installerrs))
75 vlog("# Running", cmd)
75 vlog("# Running", cmd)
76 if os.system(cmd) == 0:
76 if os.system(cmd) == 0:
77 if not verbose:
77 if not verbose:
78 os.remove(installerrs)
78 os.remove(installerrs)
79 else:
79 else:
80 f = open(installerrs)
80 f = open(installerrs)
81 for line in f:
81 for line in f:
82 print line,
82 print line,
83 f.close()
83 f.close()
84 sys.exit(1)
84 sys.exit(1)
85 os.chdir(TESTDIR)
85 os.chdir(TESTDIR)
86
86
87 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
87 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
88 os.environ["PYTHONPATH"] = PYTHONDIR
88 os.environ["PYTHONPATH"] = PYTHONDIR
89
89
90 if coverage:
90 if coverage:
91 vlog("# Installing coverage wrapper")
91 vlog("# Installing coverage wrapper")
92 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
92 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
93 if os.path.exists(COVERAGE_FILE):
93 if os.path.exists(COVERAGE_FILE):
94 os.unlink(COVERAGE_FILE)
94 os.unlink(COVERAGE_FILE)
95 # Create a wrapper script to invoke hg via coverage.py
95 # Create a wrapper script to invoke hg via coverage.py
96 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
96 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
97 f = open(os.path.join(BINDIR, 'hg'), 'w')
97 f = open(os.path.join(BINDIR, 'hg'), 'w')
98 f.write('#!' + sys.executable + '\n')
98 f.write('#!' + sys.executable + '\n')
99 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '+ \
99 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '+ \
100 '"%s", "-x", "%s"] + sys.argv[1:])\n' % (
100 '"%s", "-x", "%s"] + sys.argv[1:])\n' % (
101 os.path.join(TESTDIR, 'coverage.py'),
101 os.path.join(TESTDIR, 'coverage.py'),
102 os.path.join(BINDIR, '_hg.py')))
102 os.path.join(BINDIR, '_hg.py')))
103 f.close()
103 f.close()
104 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
104 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
105
105
106 def output_coverage():
106 def output_coverage():
107 vlog("# Producing coverage report")
107 vlog("# Producing coverage report")
108 omit = [BINDIR, TESTDIR, PYTHONDIR]
108 omit = [BINDIR, TESTDIR, PYTHONDIR]
109 if not options.cover_stdlib:
109 if not options.cover_stdlib:
110 # Exclude as system paths (ignoring empty strings seen on win)
110 # Exclude as system paths (ignoring empty strings seen on win)
111 omit += [x for x in sys.path if x != '']
111 omit += [x for x in sys.path if x != '']
112 omit = ','.join(omit)
112 omit = ','.join(omit)
113 os.chdir(PYTHONDIR)
113 os.chdir(PYTHONDIR)
114 cmd = '"%s" "%s" -r "--omit=%s"' % (
114 cmd = '"%s" "%s" -r "--omit=%s"' % (
115 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
115 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
116 vlog("# Running: "+cmd)
116 vlog("# Running: "+cmd)
117 os.system(cmd)
117 os.system(cmd)
118 if options.annotate:
118 if options.annotate:
119 adir = os.path.join(TESTDIR, 'annotated')
119 adir = os.path.join(TESTDIR, 'annotated')
120 if not os.path.isdir(adir):
120 if not os.path.isdir(adir):
121 os.mkdir(adir)
121 os.mkdir(adir)
122 cmd = '"%s" "%s" -a "--directory=%s" "--omit=%s"' % (
122 cmd = '"%s" "%s" -a "--directory=%s" "--omit=%s"' % (
123 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
123 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
124 adir, omit)
124 adir, omit)
125 vlog("# Running: "+cmd)
125 vlog("# Running: "+cmd)
126 os.system(cmd)
126 os.system(cmd)
127
127
128 def run(cmd, split_lines=True):
128 def run(cmd, split_lines=True):
129 """Run command in a sub-process, capturing the output (stdout and stderr).
129 """Run command in a sub-process, capturing the output (stdout and stderr).
130 Return the exist code, and output."""
130 Return the exist code, and output."""
131 # TODO: Use subprocess.Popen if we're running on Python 2.4
131 # TODO: Use subprocess.Popen if we're running on Python 2.4
132 if os.name == 'nt':
132 if os.name == 'nt':
133 tochild, fromchild = os.popen4(cmd)
133 tochild, fromchild = os.popen4(cmd)
134 tochild.close()
134 tochild.close()
135 output = fromchild.read()
135 output = fromchild.read()
136 ret = fromchild.close()
136 ret = fromchild.close()
137 if ret == None:
137 if ret == None:
138 ret = 0
138 ret = 0
139 else:
139 else:
140 proc = popen2.Popen4(cmd)
140 proc = popen2.Popen4(cmd)
141 proc.tochild.close()
141 proc.tochild.close()
142 output = proc.fromchild.read()
142 output = proc.fromchild.read()
143 ret = proc.wait()
143 ret = proc.wait()
144 if split_lines:
144 if split_lines:
145 output = output.splitlines()
145 output = output.splitlines()
146 return ret, output
146 return ret, output
147
147
148 def run_one(test):
148 def run_one(test):
149 vlog("# Test", test)
149 vlog("# Test", test)
150 if not verbose:
150 if not verbose:
151 sys.stdout.write('.')
151 sys.stdout.write('.')
152 sys.stdout.flush()
152 sys.stdout.flush()
153
153
154 err = os.path.join(TESTDIR, test+".err")
154 err = os.path.join(TESTDIR, test+".err")
155 ref = os.path.join(TESTDIR, test+".out")
155 ref = os.path.join(TESTDIR, test+".out")
156
156
157 if os.path.exists(err):
157 if os.path.exists(err):
158 os.remove(err) # Remove any previous output files
158 os.remove(err) # Remove any previous output files
159
159
160 # Make a tmp subdirectory to work in
160 # Make a tmp subdirectory to work in
161 tmpd = os.path.join(HGTMP, test)
161 tmpd = os.path.join(HGTMP, test)
162 os.mkdir(tmpd)
162 os.mkdir(tmpd)
163 os.chdir(tmpd)
163 os.chdir(tmpd)
164
164
165 if test.endswith(".py"):
165 if test.endswith(".py"):
166 cmd = '%s "%s"' % (sys.executable, os.path.join(TESTDIR, test))
166 cmd = '%s "%s"' % (sys.executable, os.path.join(TESTDIR, test))
167 else:
167 else:
168 cmd = '"%s"' % (os.path.join(TESTDIR, test))
168 cmd = '"%s"' % (os.path.join(TESTDIR, test))
169
169
170 # To reliably get the error code from batch files on WinXP,
170 # To reliably get the error code from batch files on WinXP,
171 # the "cmd /c call" prefix is needed. Grrr
171 # the "cmd /c call" prefix is needed. Grrr
172 if os.name == 'nt' and test.endswith(".bat"):
172 if os.name == 'nt' and test.endswith(".bat"):
173 cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test))
173 cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test))
174
174
175 vlog("# Running", cmd)
175 vlog("# Running", cmd)
176 ret, out = run(cmd)
176 ret, out = run(cmd)
177 vlog("# Ret was:", ret)
177 vlog("# Ret was:", ret)
178
178
179 if ret == 0:
179 diffret = 0
180 # If reference output file exists, check test output against it
180 # If reference output file exists, check test output against it
181 if os.path.exists(ref):
181 if os.path.exists(ref):
182 f = open(ref, "r")
182 f = open(ref, "r")
183 ref_out = f.read().splitlines()
183 ref_out = f.read().splitlines()
184 f.close()
184 f.close()
185 if out != ref_out:
185 if out != ref_out:
186 ret = 1
186 diffret = 1
187 print "\nERROR: %s output changed" % (test)
187 print "\nERROR: %s output changed" % (test)
188 show_diff(ref_out, out)
188 show_diff(ref_out, out)
189 else:
189 if ret:
190 print "\nERROR: %s failed with error code %d" % (test, ret)
190 print "\nERROR: %s failed with error code %d" % (test, ret)
191 elif diffret:
192 ret = diffret
191
193
192 if ret != 0: # Save errors to a file for diagnosis
194 if ret != 0: # Save errors to a file for diagnosis
193 f = open(err, "w")
195 f = open(err, "w")
194 for line in out:
196 for line in out:
195 f.write(line)
197 f.write(line)
196 f.write("\n")
198 f.write("\n")
197 f.close()
199 f.close()
198
200
199 os.chdir(TESTDIR)
201 os.chdir(TESTDIR)
200 shutil.rmtree(tmpd, True)
202 shutil.rmtree(tmpd, True)
201 return ret == 0
203 return ret == 0
202
204
203
205
204 os.umask(022)
206 os.umask(022)
205
207
206 check_required_tools()
208 check_required_tools()
207
209
208 # Reset some environment variables to well-known values so that
210 # Reset some environment variables to well-known values so that
209 # the tests produce repeatable output.
211 # the tests produce repeatable output.
210 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
212 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
211 os.environ['TZ'] = 'GMT'
213 os.environ['TZ'] = 'GMT'
212
214
213 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
215 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
214 os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
216 os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
215 os.environ["HGUSER"] = "test"
217 os.environ["HGUSER"] = "test"
216 os.environ["HGRCPATH"] = ""
218 os.environ["HGRCPATH"] = ""
217
219
218 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
220 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
219 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
221 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
220 vlog("# Using TESTDIR", TESTDIR)
222 vlog("# Using TESTDIR", TESTDIR)
221 vlog("# Using HGTMP", HGTMP)
223 vlog("# Using HGTMP", HGTMP)
222
224
223 INST = os.path.join(HGTMP, "install")
225 INST = os.path.join(HGTMP, "install")
224 BINDIR = os.path.join(INST, "bin")
226 BINDIR = os.path.join(INST, "bin")
225 PYTHONDIR = os.path.join(INST, "lib", "python")
227 PYTHONDIR = os.path.join(INST, "lib", "python")
226 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
228 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
227
229
228 try:
230 try:
229 install_hg()
231 install_hg()
230
232
231 tests = 0
233 tests = 0
232 failed = 0
234 failed = 0
233
235
234 if len(args) == 0:
236 if len(args) == 0:
235 args = os.listdir(".")
237 args = os.listdir(".")
236 for test in args:
238 for test in args:
237 if test.startswith("test-"):
239 if test.startswith("test-"):
238 if '~' in test or re.search(r'\.(out|err)$', test):
240 if '~' in test or re.search(r'\.(out|err)$', test):
239 continue
241 continue
240 if not run_one(test):
242 if not run_one(test):
241 failed += 1
243 failed += 1
242 tests += 1
244 tests += 1
243
245
244 print "\n# Ran %d tests, %d failed." % (tests, failed)
246 print "\n# Ran %d tests, %d failed." % (tests, failed)
245 if coverage:
247 if coverage:
246 output_coverage()
248 output_coverage()
247 finally:
249 finally:
248 cleanup_exit()
250 cleanup_exit()
249
251
250 if failed:
252 if failed:
251 sys.exit(1)
253 sys.exit(1)
General Comments 0
You need to be logged in to leave comments. Login now