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