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