##// END OF EJS Templates
run-tests.py: print diff if reference output not existing.
Vadim Gelfer -
r2246:3fd603eb default
parent child Browse files
Show More
@@ -1,253 +1,255 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 diffret = 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 else:
186 diffret = 1
186 ref_out = ''
187 print "\nERROR: %s output changed" % (test)
187 if out != ref_out:
188 show_diff(ref_out, out)
188 diffret = 1
189 print "\nERROR: %s output changed" % (test)
190 show_diff(ref_out, out)
189 if ret:
191 if ret:
190 print "\nERROR: %s failed with error code %d" % (test, ret)
192 print "\nERROR: %s failed with error code %d" % (test, ret)
191 elif diffret:
193 elif diffret:
192 ret = diffret
194 ret = diffret
193
195
194 if ret != 0: # Save errors to a file for diagnosis
196 if ret != 0: # Save errors to a file for diagnosis
195 f = open(err, "w")
197 f = open(err, "w")
196 for line in out:
198 for line in out:
197 f.write(line)
199 f.write(line)
198 f.write("\n")
200 f.write("\n")
199 f.close()
201 f.close()
200
202
201 os.chdir(TESTDIR)
203 os.chdir(TESTDIR)
202 shutil.rmtree(tmpd, True)
204 shutil.rmtree(tmpd, True)
203 return ret == 0
205 return ret == 0
204
206
205
207
206 os.umask(022)
208 os.umask(022)
207
209
208 check_required_tools()
210 check_required_tools()
209
211
210 # Reset some environment variables to well-known values so that
212 # Reset some environment variables to well-known values so that
211 # the tests produce repeatable output.
213 # the tests produce repeatable output.
212 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
214 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
213 os.environ['TZ'] = 'GMT'
215 os.environ['TZ'] = 'GMT'
214
216
215 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
217 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
216 os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
218 os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
217 os.environ["HGUSER"] = "test"
219 os.environ["HGUSER"] = "test"
218 os.environ["HGRCPATH"] = ""
220 os.environ["HGRCPATH"] = ""
219
221
220 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
222 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
221 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
223 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
222 vlog("# Using TESTDIR", TESTDIR)
224 vlog("# Using TESTDIR", TESTDIR)
223 vlog("# Using HGTMP", HGTMP)
225 vlog("# Using HGTMP", HGTMP)
224
226
225 INST = os.path.join(HGTMP, "install")
227 INST = os.path.join(HGTMP, "install")
226 BINDIR = os.path.join(INST, "bin")
228 BINDIR = os.path.join(INST, "bin")
227 PYTHONDIR = os.path.join(INST, "lib", "python")
229 PYTHONDIR = os.path.join(INST, "lib", "python")
228 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
230 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
229
231
230 try:
232 try:
231 install_hg()
233 install_hg()
232
234
233 tests = 0
235 tests = 0
234 failed = 0
236 failed = 0
235
237
236 if len(args) == 0:
238 if len(args) == 0:
237 args = os.listdir(".")
239 args = os.listdir(".")
238 for test in args:
240 for test in args:
239 if test.startswith("test-"):
241 if test.startswith("test-"):
240 if '~' in test or re.search(r'\.(out|err)$', test):
242 if '~' in test or re.search(r'\.(out|err)$', test):
241 continue
243 continue
242 if not run_one(test):
244 if not run_one(test):
243 failed += 1
245 failed += 1
244 tests += 1
246 tests += 1
245
247
246 print "\n# Ran %d tests, %d failed." % (tests, failed)
248 print "\n# Ran %d tests, %d failed." % (tests, failed)
247 if coverage:
249 if coverage:
248 output_coverage()
250 output_coverage()
249 finally:
251 finally:
250 cleanup_exit()
252 cleanup_exit()
251
253
252 if failed:
254 if failed:
253 sys.exit(1)
255 sys.exit(1)
General Comments 0
You need to be logged in to leave comments. Login now