##// END OF EJS Templates
python3: update killdaemons and run-tests print and exception syntax...
Augie Fackler -
r25031:0adc22a0 default
parent child Browse files
Show More
@@ -1,91 +1,91 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 import os, sys, time, errno, signal
3 import os, sys, time, errno, signal
4
4
5 if os.name =='nt':
5 if os.name =='nt':
6 import ctypes
6 import ctypes
7
7
8 def _check(ret, expectederr=None):
8 def _check(ret, expectederr=None):
9 if ret == 0:
9 if ret == 0:
10 winerrno = ctypes.GetLastError()
10 winerrno = ctypes.GetLastError()
11 if winerrno == expectederr:
11 if winerrno == expectederr:
12 return True
12 return True
13 raise ctypes.WinError(winerrno)
13 raise ctypes.WinError(winerrno)
14
14
15 def kill(pid, logfn, tryhard=True):
15 def kill(pid, logfn, tryhard=True):
16 logfn('# Killing daemon process %d' % pid)
16 logfn('# Killing daemon process %d' % pid)
17 PROCESS_TERMINATE = 1
17 PROCESS_TERMINATE = 1
18 PROCESS_QUERY_INFORMATION = 0x400
18 PROCESS_QUERY_INFORMATION = 0x400
19 SYNCHRONIZE = 0x00100000
19 SYNCHRONIZE = 0x00100000
20 WAIT_OBJECT_0 = 0
20 WAIT_OBJECT_0 = 0
21 WAIT_TIMEOUT = 258
21 WAIT_TIMEOUT = 258
22 handle = ctypes.windll.kernel32.OpenProcess(
22 handle = ctypes.windll.kernel32.OpenProcess(
23 PROCESS_TERMINATE|SYNCHRONIZE|PROCESS_QUERY_INFORMATION,
23 PROCESS_TERMINATE|SYNCHRONIZE|PROCESS_QUERY_INFORMATION,
24 False, pid)
24 False, pid)
25 if handle == 0:
25 if handle == 0:
26 _check(0, 87) # err 87 when process not found
26 _check(0, 87) # err 87 when process not found
27 return # process not found, already finished
27 return # process not found, already finished
28 try:
28 try:
29 r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
29 r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
30 if r == WAIT_OBJECT_0:
30 if r == WAIT_OBJECT_0:
31 pass # terminated, but process handle still available
31 pass # terminated, but process handle still available
32 elif r == WAIT_TIMEOUT:
32 elif r == WAIT_TIMEOUT:
33 _check(ctypes.windll.kernel32.TerminateProcess(handle, -1))
33 _check(ctypes.windll.kernel32.TerminateProcess(handle, -1))
34 else:
34 else:
35 _check(r)
35 _check(r)
36
36
37 # TODO?: forcefully kill when timeout
37 # TODO?: forcefully kill when timeout
38 # and ?shorter waiting time? when tryhard==True
38 # and ?shorter waiting time? when tryhard==True
39 r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
39 r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
40 # timeout = 100 ms
40 # timeout = 100 ms
41 if r == WAIT_OBJECT_0:
41 if r == WAIT_OBJECT_0:
42 pass # process is terminated
42 pass # process is terminated
43 elif r == WAIT_TIMEOUT:
43 elif r == WAIT_TIMEOUT:
44 logfn('# Daemon process %d is stuck')
44 logfn('# Daemon process %d is stuck')
45 else:
45 else:
46 _check(r) # any error
46 _check(r) # any error
47 except: #re-raises
47 except: #re-raises
48 ctypes.windll.kernel32.CloseHandle(handle) # no _check, keep error
48 ctypes.windll.kernel32.CloseHandle(handle) # no _check, keep error
49 raise
49 raise
50 _check(ctypes.windll.kernel32.CloseHandle(handle))
50 _check(ctypes.windll.kernel32.CloseHandle(handle))
51
51
52 else:
52 else:
53 def kill(pid, logfn, tryhard=True):
53 def kill(pid, logfn, tryhard=True):
54 try:
54 try:
55 os.kill(pid, 0)
55 os.kill(pid, 0)
56 logfn('# Killing daemon process %d' % pid)
56 logfn('# Killing daemon process %d' % pid)
57 os.kill(pid, signal.SIGTERM)
57 os.kill(pid, signal.SIGTERM)
58 if tryhard:
58 if tryhard:
59 for i in range(10):
59 for i in range(10):
60 time.sleep(0.05)
60 time.sleep(0.05)
61 os.kill(pid, 0)
61 os.kill(pid, 0)
62 else:
62 else:
63 time.sleep(0.1)
63 time.sleep(0.1)
64 os.kill(pid, 0)
64 os.kill(pid, 0)
65 logfn('# Daemon process %d is stuck - really killing it' % pid)
65 logfn('# Daemon process %d is stuck - really killing it' % pid)
66 os.kill(pid, signal.SIGKILL)
66 os.kill(pid, signal.SIGKILL)
67 except OSError, err:
67 except OSError as err:
68 if err.errno != errno.ESRCH:
68 if err.errno != errno.ESRCH:
69 raise
69 raise
70
70
71 def killdaemons(pidfile, tryhard=True, remove=False, logfn=None):
71 def killdaemons(pidfile, tryhard=True, remove=False, logfn=None):
72 if not logfn:
72 if not logfn:
73 logfn = lambda s: s
73 logfn = lambda s: s
74 # Kill off any leftover daemon processes
74 # Kill off any leftover daemon processes
75 try:
75 try:
76 fp = open(pidfile)
76 fp = open(pidfile)
77 for line in fp:
77 for line in fp:
78 try:
78 try:
79 pid = int(line)
79 pid = int(line)
80 except ValueError:
80 except ValueError:
81 continue
81 continue
82 kill(pid, logfn, tryhard)
82 kill(pid, logfn, tryhard)
83 fp.close()
83 fp.close()
84 if remove:
84 if remove:
85 os.unlink(pidfile)
85 os.unlink(pidfile)
86 except IOError:
86 except IOError:
87 pass
87 pass
88
88
89 if __name__ == '__main__':
89 if __name__ == '__main__':
90 path, = sys.argv[1:]
90 path, = sys.argv[1:]
91 killdaemons(path)
91 killdaemons(path)
@@ -1,2116 +1,2121 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 of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 #
38 #
39 # (You could use any subset of the tests: test-s* happens to match
39 # (You could use any subset of the tests: test-s* happens to match
40 # enough that it's worth doing parallel runs, few enough that it
40 # enough that it's worth doing parallel runs, few enough that it
41 # completes fairly quickly, includes both shell and Python scripts, and
41 # completes fairly quickly, includes both shell and Python scripts, and
42 # includes some scripts that run daemon processes.)
42 # includes some scripts that run daemon processes.)
43
43
44 from __future__ import print_function
45
44 from distutils import version
46 from distutils import version
45 import difflib
47 import difflib
46 import errno
48 import errno
47 import optparse
49 import optparse
48 import os
50 import os
49 import shutil
51 import shutil
50 import subprocess
52 import subprocess
51 import signal
53 import signal
52 import socket
54 import socket
53 import sys
55 import sys
54 import tempfile
56 import tempfile
55 import time
57 import time
56 import random
58 import random
57 import re
59 import re
58 import threading
60 import threading
59 import killdaemons as killmod
61 import killdaemons as killmod
60 import Queue as queue
62 try:
63 import Queue as queue
64 except ImportError:
65 import queue
61 from xml.dom import minidom
66 from xml.dom import minidom
62 import unittest
67 import unittest
63
68
64 try:
69 try:
65 import json
70 import json
66 except ImportError:
71 except ImportError:
67 try:
72 try:
68 import simplejson as json
73 import simplejson as json
69 except ImportError:
74 except ImportError:
70 json = None
75 json = None
71
76
72 processlock = threading.Lock()
77 processlock = threading.Lock()
73
78
74 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
79 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
75 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
80 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
76 # zombies but it's pretty harmless even if we do.
81 # zombies but it's pretty harmless even if we do.
77 if sys.version_info < (2, 5):
82 if sys.version_info < (2, 5):
78 subprocess._cleanup = lambda: None
83 subprocess._cleanup = lambda: None
79
84
80 wifexited = getattr(os, "WIFEXITED", lambda x: False)
85 wifexited = getattr(os, "WIFEXITED", lambda x: False)
81
86
82 def checkportisavailable(port):
87 def checkportisavailable(port):
83 """return true if a port seems free to bind on localhost"""
88 """return true if a port seems free to bind on localhost"""
84 try:
89 try:
85 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
90 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
86 s.bind(('localhost', port))
91 s.bind(('localhost', port))
87 s.close()
92 s.close()
88 return True
93 return True
89 except socket.error, exc:
94 except socket.error as exc:
90 if not exc.errno == errno.EADDRINUSE:
95 if not exc.errno == errno.EADDRINUSE:
91 raise
96 raise
92 return False
97 return False
93
98
94 closefds = os.name == 'posix'
99 closefds = os.name == 'posix'
95 def Popen4(cmd, wd, timeout, env=None):
100 def Popen4(cmd, wd, timeout, env=None):
96 processlock.acquire()
101 processlock.acquire()
97 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
102 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
98 close_fds=closefds,
103 close_fds=closefds,
99 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
104 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
100 stderr=subprocess.STDOUT)
105 stderr=subprocess.STDOUT)
101 processlock.release()
106 processlock.release()
102
107
103 p.fromchild = p.stdout
108 p.fromchild = p.stdout
104 p.tochild = p.stdin
109 p.tochild = p.stdin
105 p.childerr = p.stderr
110 p.childerr = p.stderr
106
111
107 p.timeout = False
112 p.timeout = False
108 if timeout:
113 if timeout:
109 def t():
114 def t():
110 start = time.time()
115 start = time.time()
111 while time.time() - start < timeout and p.returncode is None:
116 while time.time() - start < timeout and p.returncode is None:
112 time.sleep(.1)
117 time.sleep(.1)
113 p.timeout = True
118 p.timeout = True
114 if p.returncode is None:
119 if p.returncode is None:
115 terminate(p)
120 terminate(p)
116 threading.Thread(target=t).start()
121 threading.Thread(target=t).start()
117
122
118 return p
123 return p
119
124
120 PYTHON = sys.executable.replace('\\', '/')
125 PYTHON = sys.executable.replace('\\', '/')
121 IMPL_PATH = 'PYTHONPATH'
126 IMPL_PATH = 'PYTHONPATH'
122 if 'java' in sys.platform:
127 if 'java' in sys.platform:
123 IMPL_PATH = 'JYTHONPATH'
128 IMPL_PATH = 'JYTHONPATH'
124
129
125 defaults = {
130 defaults = {
126 'jobs': ('HGTEST_JOBS', 1),
131 'jobs': ('HGTEST_JOBS', 1),
127 'timeout': ('HGTEST_TIMEOUT', 180),
132 'timeout': ('HGTEST_TIMEOUT', 180),
128 'port': ('HGTEST_PORT', 20059),
133 'port': ('HGTEST_PORT', 20059),
129 'shell': ('HGTEST_SHELL', 'sh'),
134 'shell': ('HGTEST_SHELL', 'sh'),
130 }
135 }
131
136
132 def parselistfiles(files, listtype, warn=True):
137 def parselistfiles(files, listtype, warn=True):
133 entries = dict()
138 entries = dict()
134 for filename in files:
139 for filename in files:
135 try:
140 try:
136 path = os.path.expanduser(os.path.expandvars(filename))
141 path = os.path.expanduser(os.path.expandvars(filename))
137 f = open(path, "rb")
142 f = open(path, "rb")
138 except IOError, err:
143 except IOError as err:
139 if err.errno != errno.ENOENT:
144 if err.errno != errno.ENOENT:
140 raise
145 raise
141 if warn:
146 if warn:
142 print "warning: no such %s file: %s" % (listtype, filename)
147 print("warning: no such %s file: %s" % (listtype, filename))
143 continue
148 continue
144
149
145 for line in f.readlines():
150 for line in f.readlines():
146 line = line.split('#', 1)[0].strip()
151 line = line.split('#', 1)[0].strip()
147 if line:
152 if line:
148 entries[line] = filename
153 entries[line] = filename
149
154
150 f.close()
155 f.close()
151 return entries
156 return entries
152
157
153 def getparser():
158 def getparser():
154 """Obtain the OptionParser used by the CLI."""
159 """Obtain the OptionParser used by the CLI."""
155 parser = optparse.OptionParser("%prog [options] [tests]")
160 parser = optparse.OptionParser("%prog [options] [tests]")
156
161
157 # keep these sorted
162 # keep these sorted
158 parser.add_option("--blacklist", action="append",
163 parser.add_option("--blacklist", action="append",
159 help="skip tests listed in the specified blacklist file")
164 help="skip tests listed in the specified blacklist file")
160 parser.add_option("--whitelist", action="append",
165 parser.add_option("--whitelist", action="append",
161 help="always run tests listed in the specified whitelist file")
166 help="always run tests listed in the specified whitelist file")
162 parser.add_option("--changed", type="string",
167 parser.add_option("--changed", type="string",
163 help="run tests that are changed in parent rev or working directory")
168 help="run tests that are changed in parent rev or working directory")
164 parser.add_option("-C", "--annotate", action="store_true",
169 parser.add_option("-C", "--annotate", action="store_true",
165 help="output files annotated with coverage")
170 help="output files annotated with coverage")
166 parser.add_option("-c", "--cover", action="store_true",
171 parser.add_option("-c", "--cover", action="store_true",
167 help="print a test coverage report")
172 help="print a test coverage report")
168 parser.add_option("-d", "--debug", action="store_true",
173 parser.add_option("-d", "--debug", action="store_true",
169 help="debug mode: write output of test scripts to console"
174 help="debug mode: write output of test scripts to console"
170 " rather than capturing and diffing it (disables timeout)")
175 " rather than capturing and diffing it (disables timeout)")
171 parser.add_option("-f", "--first", action="store_true",
176 parser.add_option("-f", "--first", action="store_true",
172 help="exit on the first test failure")
177 help="exit on the first test failure")
173 parser.add_option("-H", "--htmlcov", action="store_true",
178 parser.add_option("-H", "--htmlcov", action="store_true",
174 help="create an HTML report of the coverage of the files")
179 help="create an HTML report of the coverage of the files")
175 parser.add_option("-i", "--interactive", action="store_true",
180 parser.add_option("-i", "--interactive", action="store_true",
176 help="prompt to accept changed output")
181 help="prompt to accept changed output")
177 parser.add_option("-j", "--jobs", type="int",
182 parser.add_option("-j", "--jobs", type="int",
178 help="number of jobs to run in parallel"
183 help="number of jobs to run in parallel"
179 " (default: $%s or %d)" % defaults['jobs'])
184 " (default: $%s or %d)" % defaults['jobs'])
180 parser.add_option("--keep-tmpdir", action="store_true",
185 parser.add_option("--keep-tmpdir", action="store_true",
181 help="keep temporary directory after running tests")
186 help="keep temporary directory after running tests")
182 parser.add_option("-k", "--keywords",
187 parser.add_option("-k", "--keywords",
183 help="run tests matching keywords")
188 help="run tests matching keywords")
184 parser.add_option("-l", "--local", action="store_true",
189 parser.add_option("-l", "--local", action="store_true",
185 help="shortcut for --with-hg=<testdir>/../hg")
190 help="shortcut for --with-hg=<testdir>/../hg")
186 parser.add_option("--loop", action="store_true",
191 parser.add_option("--loop", action="store_true",
187 help="loop tests repeatedly")
192 help="loop tests repeatedly")
188 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
193 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
189 help="run each test N times (default=1)", default=1)
194 help="run each test N times (default=1)", default=1)
190 parser.add_option("-n", "--nodiff", action="store_true",
195 parser.add_option("-n", "--nodiff", action="store_true",
191 help="skip showing test changes")
196 help="skip showing test changes")
192 parser.add_option("-p", "--port", type="int",
197 parser.add_option("-p", "--port", type="int",
193 help="port on which servers should listen"
198 help="port on which servers should listen"
194 " (default: $%s or %d)" % defaults['port'])
199 " (default: $%s or %d)" % defaults['port'])
195 parser.add_option("--compiler", type="string",
200 parser.add_option("--compiler", type="string",
196 help="compiler to build with")
201 help="compiler to build with")
197 parser.add_option("--pure", action="store_true",
202 parser.add_option("--pure", action="store_true",
198 help="use pure Python code instead of C extensions")
203 help="use pure Python code instead of C extensions")
199 parser.add_option("-R", "--restart", action="store_true",
204 parser.add_option("-R", "--restart", action="store_true",
200 help="restart at last error")
205 help="restart at last error")
201 parser.add_option("-r", "--retest", action="store_true",
206 parser.add_option("-r", "--retest", action="store_true",
202 help="retest failed tests")
207 help="retest failed tests")
203 parser.add_option("-S", "--noskips", action="store_true",
208 parser.add_option("-S", "--noskips", action="store_true",
204 help="don't report skip tests verbosely")
209 help="don't report skip tests verbosely")
205 parser.add_option("--shell", type="string",
210 parser.add_option("--shell", type="string",
206 help="shell to use (default: $%s or %s)" % defaults['shell'])
211 help="shell to use (default: $%s or %s)" % defaults['shell'])
207 parser.add_option("-t", "--timeout", type="int",
212 parser.add_option("-t", "--timeout", type="int",
208 help="kill errant tests after TIMEOUT seconds"
213 help="kill errant tests after TIMEOUT seconds"
209 " (default: $%s or %d)" % defaults['timeout'])
214 " (default: $%s or %d)" % defaults['timeout'])
210 parser.add_option("--time", action="store_true",
215 parser.add_option("--time", action="store_true",
211 help="time how long each test takes")
216 help="time how long each test takes")
212 parser.add_option("--json", action="store_true",
217 parser.add_option("--json", action="store_true",
213 help="store test result data in 'report.json' file")
218 help="store test result data in 'report.json' file")
214 parser.add_option("--tmpdir", type="string",
219 parser.add_option("--tmpdir", type="string",
215 help="run tests in the given temporary directory"
220 help="run tests in the given temporary directory"
216 " (implies --keep-tmpdir)")
221 " (implies --keep-tmpdir)")
217 parser.add_option("-v", "--verbose", action="store_true",
222 parser.add_option("-v", "--verbose", action="store_true",
218 help="output verbose messages")
223 help="output verbose messages")
219 parser.add_option("--xunit", type="string",
224 parser.add_option("--xunit", type="string",
220 help="record xunit results at specified path")
225 help="record xunit results at specified path")
221 parser.add_option("--view", type="string",
226 parser.add_option("--view", type="string",
222 help="external diff viewer")
227 help="external diff viewer")
223 parser.add_option("--with-hg", type="string",
228 parser.add_option("--with-hg", type="string",
224 metavar="HG",
229 metavar="HG",
225 help="test using specified hg script rather than a "
230 help="test using specified hg script rather than a "
226 "temporary installation")
231 "temporary installation")
227 parser.add_option("-3", "--py3k-warnings", action="store_true",
232 parser.add_option("-3", "--py3k-warnings", action="store_true",
228 help="enable Py3k warnings on Python 2.6+")
233 help="enable Py3k warnings on Python 2.6+")
229 parser.add_option('--extra-config-opt', action="append",
234 parser.add_option('--extra-config-opt', action="append",
230 help='set the given config opt in the test hgrc')
235 help='set the given config opt in the test hgrc')
231 parser.add_option('--random', action="store_true",
236 parser.add_option('--random', action="store_true",
232 help='run tests in random order')
237 help='run tests in random order')
233
238
234 for option, (envvar, default) in defaults.items():
239 for option, (envvar, default) in defaults.items():
235 defaults[option] = type(default)(os.environ.get(envvar, default))
240 defaults[option] = type(default)(os.environ.get(envvar, default))
236 parser.set_defaults(**defaults)
241 parser.set_defaults(**defaults)
237
242
238 return parser
243 return parser
239
244
240 def parseargs(args, parser):
245 def parseargs(args, parser):
241 """Parse arguments with our OptionParser and validate results."""
246 """Parse arguments with our OptionParser and validate results."""
242 (options, args) = parser.parse_args(args)
247 (options, args) = parser.parse_args(args)
243
248
244 # jython is always pure
249 # jython is always pure
245 if 'java' in sys.platform or '__pypy__' in sys.modules:
250 if 'java' in sys.platform or '__pypy__' in sys.modules:
246 options.pure = True
251 options.pure = True
247
252
248 if options.with_hg:
253 if options.with_hg:
249 options.with_hg = os.path.expanduser(options.with_hg)
254 options.with_hg = os.path.expanduser(options.with_hg)
250 if not (os.path.isfile(options.with_hg) and
255 if not (os.path.isfile(options.with_hg) and
251 os.access(options.with_hg, os.X_OK)):
256 os.access(options.with_hg, os.X_OK)):
252 parser.error('--with-hg must specify an executable hg script')
257 parser.error('--with-hg must specify an executable hg script')
253 if not os.path.basename(options.with_hg) == 'hg':
258 if not os.path.basename(options.with_hg) == 'hg':
254 sys.stderr.write('warning: --with-hg should specify an hg script\n')
259 sys.stderr.write('warning: --with-hg should specify an hg script\n')
255 if options.local:
260 if options.local:
256 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
261 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
257 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
262 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
258 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
263 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
259 parser.error('--local specified, but %r not found or not executable'
264 parser.error('--local specified, but %r not found or not executable'
260 % hgbin)
265 % hgbin)
261 options.with_hg = hgbin
266 options.with_hg = hgbin
262
267
263 options.anycoverage = options.cover or options.annotate or options.htmlcov
268 options.anycoverage = options.cover or options.annotate or options.htmlcov
264 if options.anycoverage:
269 if options.anycoverage:
265 try:
270 try:
266 import coverage
271 import coverage
267 covver = version.StrictVersion(coverage.__version__).version
272 covver = version.StrictVersion(coverage.__version__).version
268 if covver < (3, 3):
273 if covver < (3, 3):
269 parser.error('coverage options require coverage 3.3 or later')
274 parser.error('coverage options require coverage 3.3 or later')
270 except ImportError:
275 except ImportError:
271 parser.error('coverage options now require the coverage package')
276 parser.error('coverage options now require the coverage package')
272
277
273 if options.anycoverage and options.local:
278 if options.anycoverage and options.local:
274 # this needs some path mangling somewhere, I guess
279 # this needs some path mangling somewhere, I guess
275 parser.error("sorry, coverage options do not work when --local "
280 parser.error("sorry, coverage options do not work when --local "
276 "is specified")
281 "is specified")
277
282
278 if options.anycoverage and options.with_hg:
283 if options.anycoverage and options.with_hg:
279 parser.error("sorry, coverage options do not work when --with-hg "
284 parser.error("sorry, coverage options do not work when --with-hg "
280 "is specified")
285 "is specified")
281
286
282 global verbose
287 global verbose
283 if options.verbose:
288 if options.verbose:
284 verbose = ''
289 verbose = ''
285
290
286 if options.tmpdir:
291 if options.tmpdir:
287 options.tmpdir = os.path.expanduser(options.tmpdir)
292 options.tmpdir = os.path.expanduser(options.tmpdir)
288
293
289 if options.jobs < 1:
294 if options.jobs < 1:
290 parser.error('--jobs must be positive')
295 parser.error('--jobs must be positive')
291 if options.interactive and options.debug:
296 if options.interactive and options.debug:
292 parser.error("-i/--interactive and -d/--debug are incompatible")
297 parser.error("-i/--interactive and -d/--debug are incompatible")
293 if options.debug:
298 if options.debug:
294 if options.timeout != defaults['timeout']:
299 if options.timeout != defaults['timeout']:
295 sys.stderr.write(
300 sys.stderr.write(
296 'warning: --timeout option ignored with --debug\n')
301 'warning: --timeout option ignored with --debug\n')
297 options.timeout = 0
302 options.timeout = 0
298 if options.py3k_warnings:
303 if options.py3k_warnings:
299 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
304 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
300 parser.error('--py3k-warnings can only be used on Python 2.6+')
305 parser.error('--py3k-warnings can only be used on Python 2.6+')
301 if options.blacklist:
306 if options.blacklist:
302 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
307 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
303 if options.whitelist:
308 if options.whitelist:
304 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
309 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
305 else:
310 else:
306 options.whitelisted = {}
311 options.whitelisted = {}
307
312
308 return (options, args)
313 return (options, args)
309
314
310 def rename(src, dst):
315 def rename(src, dst):
311 """Like os.rename(), trade atomicity and opened files friendliness
316 """Like os.rename(), trade atomicity and opened files friendliness
312 for existing destination support.
317 for existing destination support.
313 """
318 """
314 shutil.copy(src, dst)
319 shutil.copy(src, dst)
315 os.remove(src)
320 os.remove(src)
316
321
317 def getdiff(expected, output, ref, err):
322 def getdiff(expected, output, ref, err):
318 servefail = False
323 servefail = False
319 lines = []
324 lines = []
320 for line in difflib.unified_diff(expected, output, ref, err):
325 for line in difflib.unified_diff(expected, output, ref, err):
321 if line.startswith('+++') or line.startswith('---'):
326 if line.startswith('+++') or line.startswith('---'):
322 line = line.replace('\\', '/')
327 line = line.replace('\\', '/')
323 if line.endswith(' \n'):
328 if line.endswith(' \n'):
324 line = line[:-2] + '\n'
329 line = line[:-2] + '\n'
325 lines.append(line)
330 lines.append(line)
326 if not servefail and line.startswith(
331 if not servefail and line.startswith(
327 '+ abort: child process failed to start'):
332 '+ abort: child process failed to start'):
328 servefail = True
333 servefail = True
329
334
330 return servefail, lines
335 return servefail, lines
331
336
332 verbose = False
337 verbose = False
333 def vlog(*msg):
338 def vlog(*msg):
334 """Log only when in verbose mode."""
339 """Log only when in verbose mode."""
335 if verbose is False:
340 if verbose is False:
336 return
341 return
337
342
338 return log(*msg)
343 return log(*msg)
339
344
340 # Bytes that break XML even in a CDATA block: control characters 0-31
345 # Bytes that break XML even in a CDATA block: control characters 0-31
341 # sans \t, \n and \r
346 # sans \t, \n and \r
342 CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]")
347 CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]")
343
348
344 def cdatasafe(data):
349 def cdatasafe(data):
345 """Make a string safe to include in a CDATA block.
350 """Make a string safe to include in a CDATA block.
346
351
347 Certain control characters are illegal in a CDATA block, and
352 Certain control characters are illegal in a CDATA block, and
348 there's no way to include a ]]> in a CDATA either. This function
353 there's no way to include a ]]> in a CDATA either. This function
349 replaces illegal bytes with ? and adds a space between the ]] so
354 replaces illegal bytes with ? and adds a space between the ]] so
350 that it won't break the CDATA block.
355 that it won't break the CDATA block.
351 """
356 """
352 return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>')
357 return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>')
353
358
354 def log(*msg):
359 def log(*msg):
355 """Log something to stdout.
360 """Log something to stdout.
356
361
357 Arguments are strings to print.
362 Arguments are strings to print.
358 """
363 """
359 iolock.acquire()
364 iolock.acquire()
360 if verbose:
365 if verbose:
361 print verbose,
366 print(verbose, end=' ')
362 for m in msg:
367 for m in msg:
363 print m,
368 print(m, end=' ')
364 print
369 print()
365 sys.stdout.flush()
370 sys.stdout.flush()
366 iolock.release()
371 iolock.release()
367
372
368 def terminate(proc):
373 def terminate(proc):
369 """Terminate subprocess (with fallback for Python versions < 2.6)"""
374 """Terminate subprocess (with fallback for Python versions < 2.6)"""
370 vlog('# Terminating process %d' % proc.pid)
375 vlog('# Terminating process %d' % proc.pid)
371 try:
376 try:
372 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
377 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
373 except OSError:
378 except OSError:
374 pass
379 pass
375
380
376 def killdaemons(pidfile):
381 def killdaemons(pidfile):
377 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
382 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
378 logfn=vlog)
383 logfn=vlog)
379
384
380 class Test(unittest.TestCase):
385 class Test(unittest.TestCase):
381 """Encapsulates a single, runnable test.
386 """Encapsulates a single, runnable test.
382
387
383 While this class conforms to the unittest.TestCase API, it differs in that
388 While this class conforms to the unittest.TestCase API, it differs in that
384 instances need to be instantiated manually. (Typically, unittest.TestCase
389 instances need to be instantiated manually. (Typically, unittest.TestCase
385 classes are instantiated automatically by scanning modules.)
390 classes are instantiated automatically by scanning modules.)
386 """
391 """
387
392
388 # Status code reserved for skipped tests (used by hghave).
393 # Status code reserved for skipped tests (used by hghave).
389 SKIPPED_STATUS = 80
394 SKIPPED_STATUS = 80
390
395
391 def __init__(self, path, tmpdir, keeptmpdir=False,
396 def __init__(self, path, tmpdir, keeptmpdir=False,
392 debug=False,
397 debug=False,
393 timeout=defaults['timeout'],
398 timeout=defaults['timeout'],
394 startport=defaults['port'], extraconfigopts=None,
399 startport=defaults['port'], extraconfigopts=None,
395 py3kwarnings=False, shell=None):
400 py3kwarnings=False, shell=None):
396 """Create a test from parameters.
401 """Create a test from parameters.
397
402
398 path is the full path to the file defining the test.
403 path is the full path to the file defining the test.
399
404
400 tmpdir is the main temporary directory to use for this test.
405 tmpdir is the main temporary directory to use for this test.
401
406
402 keeptmpdir determines whether to keep the test's temporary directory
407 keeptmpdir determines whether to keep the test's temporary directory
403 after execution. It defaults to removal (False).
408 after execution. It defaults to removal (False).
404
409
405 debug mode will make the test execute verbosely, with unfiltered
410 debug mode will make the test execute verbosely, with unfiltered
406 output.
411 output.
407
412
408 timeout controls the maximum run time of the test. It is ignored when
413 timeout controls the maximum run time of the test. It is ignored when
409 debug is True.
414 debug is True.
410
415
411 startport controls the starting port number to use for this test. Each
416 startport controls the starting port number to use for this test. Each
412 test will reserve 3 port numbers for execution. It is the caller's
417 test will reserve 3 port numbers for execution. It is the caller's
413 responsibility to allocate a non-overlapping port range to Test
418 responsibility to allocate a non-overlapping port range to Test
414 instances.
419 instances.
415
420
416 extraconfigopts is an iterable of extra hgrc config options. Values
421 extraconfigopts is an iterable of extra hgrc config options. Values
417 must have the form "key=value" (something understood by hgrc). Values
422 must have the form "key=value" (something understood by hgrc). Values
418 of the form "foo.key=value" will result in "[foo] key=value".
423 of the form "foo.key=value" will result in "[foo] key=value".
419
424
420 py3kwarnings enables Py3k warnings.
425 py3kwarnings enables Py3k warnings.
421
426
422 shell is the shell to execute tests in.
427 shell is the shell to execute tests in.
423 """
428 """
424
429
425 self.path = path
430 self.path = path
426 self.name = os.path.basename(path)
431 self.name = os.path.basename(path)
427 self._testdir = os.path.dirname(path)
432 self._testdir = os.path.dirname(path)
428 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
433 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
429
434
430 self._threadtmp = tmpdir
435 self._threadtmp = tmpdir
431 self._keeptmpdir = keeptmpdir
436 self._keeptmpdir = keeptmpdir
432 self._debug = debug
437 self._debug = debug
433 self._timeout = timeout
438 self._timeout = timeout
434 self._startport = startport
439 self._startport = startport
435 self._extraconfigopts = extraconfigopts or []
440 self._extraconfigopts = extraconfigopts or []
436 self._py3kwarnings = py3kwarnings
441 self._py3kwarnings = py3kwarnings
437 self._shell = shell
442 self._shell = shell
438
443
439 self._aborted = False
444 self._aborted = False
440 self._daemonpids = []
445 self._daemonpids = []
441 self._finished = None
446 self._finished = None
442 self._ret = None
447 self._ret = None
443 self._out = None
448 self._out = None
444 self._skipped = None
449 self._skipped = None
445 self._testtmp = None
450 self._testtmp = None
446
451
447 # If we're not in --debug mode and reference output file exists,
452 # If we're not in --debug mode and reference output file exists,
448 # check test output against it.
453 # check test output against it.
449 if debug:
454 if debug:
450 self._refout = None # to match "out is None"
455 self._refout = None # to match "out is None"
451 elif os.path.exists(self.refpath):
456 elif os.path.exists(self.refpath):
452 f = open(self.refpath, 'rb')
457 f = open(self.refpath, 'rb')
453 self._refout = f.read().splitlines(True)
458 self._refout = f.read().splitlines(True)
454 f.close()
459 f.close()
455 else:
460 else:
456 self._refout = []
461 self._refout = []
457
462
458 # needed to get base class __repr__ running
463 # needed to get base class __repr__ running
459 @property
464 @property
460 def _testMethodName(self):
465 def _testMethodName(self):
461 return self.name
466 return self.name
462
467
463 def __str__(self):
468 def __str__(self):
464 return self.name
469 return self.name
465
470
466 def shortDescription(self):
471 def shortDescription(self):
467 return self.name
472 return self.name
468
473
469 def setUp(self):
474 def setUp(self):
470 """Tasks to perform before run()."""
475 """Tasks to perform before run()."""
471 self._finished = False
476 self._finished = False
472 self._ret = None
477 self._ret = None
473 self._out = None
478 self._out = None
474 self._skipped = None
479 self._skipped = None
475
480
476 try:
481 try:
477 os.mkdir(self._threadtmp)
482 os.mkdir(self._threadtmp)
478 except OSError, e:
483 except OSError as e:
479 if e.errno != errno.EEXIST:
484 if e.errno != errno.EEXIST:
480 raise
485 raise
481
486
482 self._testtmp = os.path.join(self._threadtmp,
487 self._testtmp = os.path.join(self._threadtmp,
483 os.path.basename(self.path))
488 os.path.basename(self.path))
484 os.mkdir(self._testtmp)
489 os.mkdir(self._testtmp)
485
490
486 # Remove any previous output files.
491 # Remove any previous output files.
487 if os.path.exists(self.errpath):
492 if os.path.exists(self.errpath):
488 try:
493 try:
489 os.remove(self.errpath)
494 os.remove(self.errpath)
490 except OSError, e:
495 except OSError as e:
491 # We might have raced another test to clean up a .err
496 # We might have raced another test to clean up a .err
492 # file, so ignore ENOENT when removing a previous .err
497 # file, so ignore ENOENT when removing a previous .err
493 # file.
498 # file.
494 if e.errno != errno.ENOENT:
499 if e.errno != errno.ENOENT:
495 raise
500 raise
496
501
497 def run(self, result):
502 def run(self, result):
498 """Run this test and report results against a TestResult instance."""
503 """Run this test and report results against a TestResult instance."""
499 # This function is extremely similar to unittest.TestCase.run(). Once
504 # This function is extremely similar to unittest.TestCase.run(). Once
500 # we require Python 2.7 (or at least its version of unittest), this
505 # we require Python 2.7 (or at least its version of unittest), this
501 # function can largely go away.
506 # function can largely go away.
502 self._result = result
507 self._result = result
503 result.startTest(self)
508 result.startTest(self)
504 try:
509 try:
505 try:
510 try:
506 self.setUp()
511 self.setUp()
507 except (KeyboardInterrupt, SystemExit):
512 except (KeyboardInterrupt, SystemExit):
508 self._aborted = True
513 self._aborted = True
509 raise
514 raise
510 except Exception:
515 except Exception:
511 result.addError(self, sys.exc_info())
516 result.addError(self, sys.exc_info())
512 return
517 return
513
518
514 success = False
519 success = False
515 try:
520 try:
516 self.runTest()
521 self.runTest()
517 except KeyboardInterrupt:
522 except KeyboardInterrupt:
518 self._aborted = True
523 self._aborted = True
519 raise
524 raise
520 except SkipTest, e:
525 except SkipTest as e:
521 result.addSkip(self, str(e))
526 result.addSkip(self, str(e))
522 # The base class will have already counted this as a
527 # The base class will have already counted this as a
523 # test we "ran", but we want to exclude skipped tests
528 # test we "ran", but we want to exclude skipped tests
524 # from those we count towards those run.
529 # from those we count towards those run.
525 result.testsRun -= 1
530 result.testsRun -= 1
526 except IgnoreTest, e:
531 except IgnoreTest as e:
527 result.addIgnore(self, str(e))
532 result.addIgnore(self, str(e))
528 # As with skips, ignores also should be excluded from
533 # As with skips, ignores also should be excluded from
529 # the number of tests executed.
534 # the number of tests executed.
530 result.testsRun -= 1
535 result.testsRun -= 1
531 except WarnTest, e:
536 except WarnTest as e:
532 result.addWarn(self, str(e))
537 result.addWarn(self, str(e))
533 except self.failureException, e:
538 except self.failureException as e:
534 # This differs from unittest in that we don't capture
539 # This differs from unittest in that we don't capture
535 # the stack trace. This is for historical reasons and
540 # the stack trace. This is for historical reasons and
536 # this decision could be revisited in the future,
541 # this decision could be revisited in the future,
537 # especially for PythonTest instances.
542 # especially for PythonTest instances.
538 if result.addFailure(self, str(e)):
543 if result.addFailure(self, str(e)):
539 success = True
544 success = True
540 except Exception:
545 except Exception:
541 result.addError(self, sys.exc_info())
546 result.addError(self, sys.exc_info())
542 else:
547 else:
543 success = True
548 success = True
544
549
545 try:
550 try:
546 self.tearDown()
551 self.tearDown()
547 except (KeyboardInterrupt, SystemExit):
552 except (KeyboardInterrupt, SystemExit):
548 self._aborted = True
553 self._aborted = True
549 raise
554 raise
550 except Exception:
555 except Exception:
551 result.addError(self, sys.exc_info())
556 result.addError(self, sys.exc_info())
552 success = False
557 success = False
553
558
554 if success:
559 if success:
555 result.addSuccess(self)
560 result.addSuccess(self)
556 finally:
561 finally:
557 result.stopTest(self, interrupted=self._aborted)
562 result.stopTest(self, interrupted=self._aborted)
558
563
559 def runTest(self):
564 def runTest(self):
560 """Run this test instance.
565 """Run this test instance.
561
566
562 This will return a tuple describing the result of the test.
567 This will return a tuple describing the result of the test.
563 """
568 """
564 env = self._getenv()
569 env = self._getenv()
565 self._daemonpids.append(env['DAEMON_PIDS'])
570 self._daemonpids.append(env['DAEMON_PIDS'])
566 self._createhgrc(env['HGRCPATH'])
571 self._createhgrc(env['HGRCPATH'])
567
572
568 vlog('# Test', self.name)
573 vlog('# Test', self.name)
569
574
570 ret, out = self._run(env)
575 ret, out = self._run(env)
571 self._finished = True
576 self._finished = True
572 self._ret = ret
577 self._ret = ret
573 self._out = out
578 self._out = out
574
579
575 def describe(ret):
580 def describe(ret):
576 if ret < 0:
581 if ret < 0:
577 return 'killed by signal: %d' % -ret
582 return 'killed by signal: %d' % -ret
578 return 'returned error code %d' % ret
583 return 'returned error code %d' % ret
579
584
580 self._skipped = False
585 self._skipped = False
581
586
582 if ret == self.SKIPPED_STATUS:
587 if ret == self.SKIPPED_STATUS:
583 if out is None: # Debug mode, nothing to parse.
588 if out is None: # Debug mode, nothing to parse.
584 missing = ['unknown']
589 missing = ['unknown']
585 failed = None
590 failed = None
586 else:
591 else:
587 missing, failed = TTest.parsehghaveoutput(out)
592 missing, failed = TTest.parsehghaveoutput(out)
588
593
589 if not missing:
594 if not missing:
590 missing = ['skipped']
595 missing = ['skipped']
591
596
592 if failed:
597 if failed:
593 self.fail('hg have failed checking for %s' % failed[-1])
598 self.fail('hg have failed checking for %s' % failed[-1])
594 else:
599 else:
595 self._skipped = True
600 self._skipped = True
596 raise SkipTest(missing[-1])
601 raise SkipTest(missing[-1])
597 elif ret == 'timeout':
602 elif ret == 'timeout':
598 self.fail('timed out')
603 self.fail('timed out')
599 elif ret is False:
604 elif ret is False:
600 raise WarnTest('no result code from test')
605 raise WarnTest('no result code from test')
601 elif out != self._refout:
606 elif out != self._refout:
602 # Diff generation may rely on written .err file.
607 # Diff generation may rely on written .err file.
603 if (ret != 0 or out != self._refout) and not self._skipped \
608 if (ret != 0 or out != self._refout) and not self._skipped \
604 and not self._debug:
609 and not self._debug:
605 f = open(self.errpath, 'wb')
610 f = open(self.errpath, 'wb')
606 for line in out:
611 for line in out:
607 f.write(line)
612 f.write(line)
608 f.close()
613 f.close()
609
614
610 # The result object handles diff calculation for us.
615 # The result object handles diff calculation for us.
611 if self._result.addOutputMismatch(self, ret, out, self._refout):
616 if self._result.addOutputMismatch(self, ret, out, self._refout):
612 # change was accepted, skip failing
617 # change was accepted, skip failing
613 return
618 return
614
619
615 if ret:
620 if ret:
616 msg = 'output changed and ' + describe(ret)
621 msg = 'output changed and ' + describe(ret)
617 else:
622 else:
618 msg = 'output changed'
623 msg = 'output changed'
619
624
620 self.fail(msg)
625 self.fail(msg)
621 elif ret:
626 elif ret:
622 self.fail(describe(ret))
627 self.fail(describe(ret))
623
628
624 def tearDown(self):
629 def tearDown(self):
625 """Tasks to perform after run()."""
630 """Tasks to perform after run()."""
626 for entry in self._daemonpids:
631 for entry in self._daemonpids:
627 killdaemons(entry)
632 killdaemons(entry)
628 self._daemonpids = []
633 self._daemonpids = []
629
634
630 if not self._keeptmpdir:
635 if not self._keeptmpdir:
631 shutil.rmtree(self._testtmp, True)
636 shutil.rmtree(self._testtmp, True)
632 shutil.rmtree(self._threadtmp, True)
637 shutil.rmtree(self._threadtmp, True)
633
638
634 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
639 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
635 and not self._debug and self._out:
640 and not self._debug and self._out:
636 f = open(self.errpath, 'wb')
641 f = open(self.errpath, 'wb')
637 for line in self._out:
642 for line in self._out:
638 f.write(line)
643 f.write(line)
639 f.close()
644 f.close()
640
645
641 vlog("# Ret was:", self._ret, '(%s)' % self.name)
646 vlog("# Ret was:", self._ret, '(%s)' % self.name)
642
647
643 def _run(self, env):
648 def _run(self, env):
644 # This should be implemented in child classes to run tests.
649 # This should be implemented in child classes to run tests.
645 raise SkipTest('unknown test type')
650 raise SkipTest('unknown test type')
646
651
647 def abort(self):
652 def abort(self):
648 """Terminate execution of this test."""
653 """Terminate execution of this test."""
649 self._aborted = True
654 self._aborted = True
650
655
651 def _getreplacements(self):
656 def _getreplacements(self):
652 """Obtain a mapping of text replacements to apply to test output.
657 """Obtain a mapping of text replacements to apply to test output.
653
658
654 Test output needs to be normalized so it can be compared to expected
659 Test output needs to be normalized so it can be compared to expected
655 output. This function defines how some of that normalization will
660 output. This function defines how some of that normalization will
656 occur.
661 occur.
657 """
662 """
658 r = [
663 r = [
659 (r':%s\b' % self._startport, ':$HGPORT'),
664 (r':%s\b' % self._startport, ':$HGPORT'),
660 (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
665 (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
661 (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
666 (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
662 (r'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
667 (r'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
663 r'\1 (glob)'),
668 r'\1 (glob)'),
664 ]
669 ]
665
670
666 if os.name == 'nt':
671 if os.name == 'nt':
667 r.append(
672 r.append(
668 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
673 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
669 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
674 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
670 for c in self._testtmp), '$TESTTMP'))
675 for c in self._testtmp), '$TESTTMP'))
671 else:
676 else:
672 r.append((re.escape(self._testtmp), '$TESTTMP'))
677 r.append((re.escape(self._testtmp), '$TESTTMP'))
673
678
674 return r
679 return r
675
680
676 def _getenv(self):
681 def _getenv(self):
677 """Obtain environment variables to use during test execution."""
682 """Obtain environment variables to use during test execution."""
678 env = os.environ.copy()
683 env = os.environ.copy()
679 env['TESTTMP'] = self._testtmp
684 env['TESTTMP'] = self._testtmp
680 env['HOME'] = self._testtmp
685 env['HOME'] = self._testtmp
681 env["HGPORT"] = str(self._startport)
686 env["HGPORT"] = str(self._startport)
682 env["HGPORT1"] = str(self._startport + 1)
687 env["HGPORT1"] = str(self._startport + 1)
683 env["HGPORT2"] = str(self._startport + 2)
688 env["HGPORT2"] = str(self._startport + 2)
684 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
689 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
685 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
690 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
686 env["HGEDITOR"] = ('"' + sys.executable + '"'
691 env["HGEDITOR"] = ('"' + sys.executable + '"'
687 + ' -c "import sys; sys.exit(0)"')
692 + ' -c "import sys; sys.exit(0)"')
688 env["HGMERGE"] = "internal:merge"
693 env["HGMERGE"] = "internal:merge"
689 env["HGUSER"] = "test"
694 env["HGUSER"] = "test"
690 env["HGENCODING"] = "ascii"
695 env["HGENCODING"] = "ascii"
691 env["HGENCODINGMODE"] = "strict"
696 env["HGENCODINGMODE"] = "strict"
692
697
693 # Reset some environment variables to well-known values so that
698 # Reset some environment variables to well-known values so that
694 # the tests produce repeatable output.
699 # the tests produce repeatable output.
695 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
700 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
696 env['TZ'] = 'GMT'
701 env['TZ'] = 'GMT'
697 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
702 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
698 env['COLUMNS'] = '80'
703 env['COLUMNS'] = '80'
699 env['TERM'] = 'xterm'
704 env['TERM'] = 'xterm'
700
705
701 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
706 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
702 'NO_PROXY').split():
707 'NO_PROXY').split():
703 if k in env:
708 if k in env:
704 del env[k]
709 del env[k]
705
710
706 # unset env related to hooks
711 # unset env related to hooks
707 for k in env.keys():
712 for k in env.keys():
708 if k.startswith('HG_'):
713 if k.startswith('HG_'):
709 del env[k]
714 del env[k]
710
715
711 return env
716 return env
712
717
713 def _createhgrc(self, path):
718 def _createhgrc(self, path):
714 """Create an hgrc file for this test."""
719 """Create an hgrc file for this test."""
715 hgrc = open(path, 'wb')
720 hgrc = open(path, 'wb')
716 hgrc.write('[ui]\n')
721 hgrc.write('[ui]\n')
717 hgrc.write('slash = True\n')
722 hgrc.write('slash = True\n')
718 hgrc.write('interactive = False\n')
723 hgrc.write('interactive = False\n')
719 hgrc.write('mergemarkers = detailed\n')
724 hgrc.write('mergemarkers = detailed\n')
720 hgrc.write('promptecho = True\n')
725 hgrc.write('promptecho = True\n')
721 hgrc.write('[defaults]\n')
726 hgrc.write('[defaults]\n')
722 hgrc.write('backout = -d "0 0"\n')
727 hgrc.write('backout = -d "0 0"\n')
723 hgrc.write('commit = -d "0 0"\n')
728 hgrc.write('commit = -d "0 0"\n')
724 hgrc.write('shelve = --date "0 0"\n')
729 hgrc.write('shelve = --date "0 0"\n')
725 hgrc.write('tag = -d "0 0"\n')
730 hgrc.write('tag = -d "0 0"\n')
726 hgrc.write('[devel]\n')
731 hgrc.write('[devel]\n')
727 hgrc.write('all = true\n')
732 hgrc.write('all = true\n')
728 hgrc.write('[largefiles]\n')
733 hgrc.write('[largefiles]\n')
729 hgrc.write('usercache = %s\n' %
734 hgrc.write('usercache = %s\n' %
730 (os.path.join(self._testtmp, '.cache/largefiles')))
735 (os.path.join(self._testtmp, '.cache/largefiles')))
731
736
732 for opt in self._extraconfigopts:
737 for opt in self._extraconfigopts:
733 section, key = opt.split('.', 1)
738 section, key = opt.split('.', 1)
734 assert '=' in key, ('extra config opt %s must '
739 assert '=' in key, ('extra config opt %s must '
735 'have an = for assignment' % opt)
740 'have an = for assignment' % opt)
736 hgrc.write('[%s]\n%s\n' % (section, key))
741 hgrc.write('[%s]\n%s\n' % (section, key))
737 hgrc.close()
742 hgrc.close()
738
743
739 def fail(self, msg):
744 def fail(self, msg):
740 # unittest differentiates between errored and failed.
745 # unittest differentiates between errored and failed.
741 # Failed is denoted by AssertionError (by default at least).
746 # Failed is denoted by AssertionError (by default at least).
742 raise AssertionError(msg)
747 raise AssertionError(msg)
743
748
744 def _runcommand(self, cmd, env, normalizenewlines=False):
749 def _runcommand(self, cmd, env, normalizenewlines=False):
745 """Run command in a sub-process, capturing the output (stdout and
750 """Run command in a sub-process, capturing the output (stdout and
746 stderr).
751 stderr).
747
752
748 Return a tuple (exitcode, output). output is None in debug mode.
753 Return a tuple (exitcode, output). output is None in debug mode.
749 """
754 """
750 if self._debug:
755 if self._debug:
751 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
756 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
752 env=env)
757 env=env)
753 ret = proc.wait()
758 ret = proc.wait()
754 return (ret, None)
759 return (ret, None)
755
760
756 proc = Popen4(cmd, self._testtmp, self._timeout, env)
761 proc = Popen4(cmd, self._testtmp, self._timeout, env)
757 def cleanup():
762 def cleanup():
758 terminate(proc)
763 terminate(proc)
759 ret = proc.wait()
764 ret = proc.wait()
760 if ret == 0:
765 if ret == 0:
761 ret = signal.SIGTERM << 8
766 ret = signal.SIGTERM << 8
762 killdaemons(env['DAEMON_PIDS'])
767 killdaemons(env['DAEMON_PIDS'])
763 return ret
768 return ret
764
769
765 output = ''
770 output = ''
766 proc.tochild.close()
771 proc.tochild.close()
767
772
768 try:
773 try:
769 output = proc.fromchild.read()
774 output = proc.fromchild.read()
770 except KeyboardInterrupt:
775 except KeyboardInterrupt:
771 vlog('# Handling keyboard interrupt')
776 vlog('# Handling keyboard interrupt')
772 cleanup()
777 cleanup()
773 raise
778 raise
774
779
775 ret = proc.wait()
780 ret = proc.wait()
776 if wifexited(ret):
781 if wifexited(ret):
777 ret = os.WEXITSTATUS(ret)
782 ret = os.WEXITSTATUS(ret)
778
783
779 if proc.timeout:
784 if proc.timeout:
780 ret = 'timeout'
785 ret = 'timeout'
781
786
782 if ret:
787 if ret:
783 killdaemons(env['DAEMON_PIDS'])
788 killdaemons(env['DAEMON_PIDS'])
784
789
785 for s, r in self._getreplacements():
790 for s, r in self._getreplacements():
786 output = re.sub(s, r, output)
791 output = re.sub(s, r, output)
787
792
788 if normalizenewlines:
793 if normalizenewlines:
789 output = output.replace('\r\n', '\n')
794 output = output.replace('\r\n', '\n')
790
795
791 return ret, output.splitlines(True)
796 return ret, output.splitlines(True)
792
797
793 class PythonTest(Test):
798 class PythonTest(Test):
794 """A Python-based test."""
799 """A Python-based test."""
795
800
796 @property
801 @property
797 def refpath(self):
802 def refpath(self):
798 return os.path.join(self._testdir, '%s.out' % self.name)
803 return os.path.join(self._testdir, '%s.out' % self.name)
799
804
800 def _run(self, env):
805 def _run(self, env):
801 py3kswitch = self._py3kwarnings and ' -3' or ''
806 py3kswitch = self._py3kwarnings and ' -3' or ''
802 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
807 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
803 vlog("# Running", cmd)
808 vlog("# Running", cmd)
804 normalizenewlines = os.name == 'nt'
809 normalizenewlines = os.name == 'nt'
805 result = self._runcommand(cmd, env,
810 result = self._runcommand(cmd, env,
806 normalizenewlines=normalizenewlines)
811 normalizenewlines=normalizenewlines)
807 if self._aborted:
812 if self._aborted:
808 raise KeyboardInterrupt()
813 raise KeyboardInterrupt()
809
814
810 return result
815 return result
811
816
812 # This script may want to drop globs from lines matching these patterns on
817 # This script may want to drop globs from lines matching these patterns on
813 # Windows, but check-code.py wants a glob on these lines unconditionally. Don't
818 # Windows, but check-code.py wants a glob on these lines unconditionally. Don't
814 # warn if that is the case for anything matching these lines.
819 # warn if that is the case for anything matching these lines.
815 checkcodeglobpats = [
820 checkcodeglobpats = [
816 re.compile(r'^pushing to \$TESTTMP/.*[^)]$'),
821 re.compile(r'^pushing to \$TESTTMP/.*[^)]$'),
817 re.compile(r'^moving \S+/.*[^)]$'),
822 re.compile(r'^moving \S+/.*[^)]$'),
818 re.compile(r'^pulling from \$TESTTMP/.*[^)]$')
823 re.compile(r'^pulling from \$TESTTMP/.*[^)]$')
819 ]
824 ]
820
825
821 class TTest(Test):
826 class TTest(Test):
822 """A "t test" is a test backed by a .t file."""
827 """A "t test" is a test backed by a .t file."""
823
828
824 SKIPPED_PREFIX = 'skipped: '
829 SKIPPED_PREFIX = 'skipped: '
825 FAILED_PREFIX = 'hghave check failed: '
830 FAILED_PREFIX = 'hghave check failed: '
826 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
831 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
827
832
828 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
833 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
829 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256))
834 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256))
830 ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'})
835 ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'})
831
836
832 @property
837 @property
833 def refpath(self):
838 def refpath(self):
834 return os.path.join(self._testdir, self.name)
839 return os.path.join(self._testdir, self.name)
835
840
836 def _run(self, env):
841 def _run(self, env):
837 f = open(self.path, 'rb')
842 f = open(self.path, 'rb')
838 lines = f.readlines()
843 lines = f.readlines()
839 f.close()
844 f.close()
840
845
841 salt, script, after, expected = self._parsetest(lines)
846 salt, script, after, expected = self._parsetest(lines)
842
847
843 # Write out the generated script.
848 # Write out the generated script.
844 fname = '%s.sh' % self._testtmp
849 fname = '%s.sh' % self._testtmp
845 f = open(fname, 'wb')
850 f = open(fname, 'wb')
846 for l in script:
851 for l in script:
847 f.write(l)
852 f.write(l)
848 f.close()
853 f.close()
849
854
850 cmd = '%s "%s"' % (self._shell, fname)
855 cmd = '%s "%s"' % (self._shell, fname)
851 vlog("# Running", cmd)
856 vlog("# Running", cmd)
852
857
853 exitcode, output = self._runcommand(cmd, env)
858 exitcode, output = self._runcommand(cmd, env)
854
859
855 if self._aborted:
860 if self._aborted:
856 raise KeyboardInterrupt()
861 raise KeyboardInterrupt()
857
862
858 # Do not merge output if skipped. Return hghave message instead.
863 # Do not merge output if skipped. Return hghave message instead.
859 # Similarly, with --debug, output is None.
864 # Similarly, with --debug, output is None.
860 if exitcode == self.SKIPPED_STATUS or output is None:
865 if exitcode == self.SKIPPED_STATUS or output is None:
861 return exitcode, output
866 return exitcode, output
862
867
863 return self._processoutput(exitcode, output, salt, after, expected)
868 return self._processoutput(exitcode, output, salt, after, expected)
864
869
865 def _hghave(self, reqs):
870 def _hghave(self, reqs):
866 # TODO do something smarter when all other uses of hghave are gone.
871 # TODO do something smarter when all other uses of hghave are gone.
867 tdir = self._testdir.replace('\\', '/')
872 tdir = self._testdir.replace('\\', '/')
868 proc = Popen4('%s -c "%s/hghave %s"' %
873 proc = Popen4('%s -c "%s/hghave %s"' %
869 (self._shell, tdir, ' '.join(reqs)),
874 (self._shell, tdir, ' '.join(reqs)),
870 self._testtmp, 0, self._getenv())
875 self._testtmp, 0, self._getenv())
871 stdout, stderr = proc.communicate()
876 stdout, stderr = proc.communicate()
872 ret = proc.wait()
877 ret = proc.wait()
873 if wifexited(ret):
878 if wifexited(ret):
874 ret = os.WEXITSTATUS(ret)
879 ret = os.WEXITSTATUS(ret)
875 if ret == 2:
880 if ret == 2:
876 print stdout
881 print(stdout)
877 sys.exit(1)
882 sys.exit(1)
878
883
879 return ret == 0
884 return ret == 0
880
885
881 def _parsetest(self, lines):
886 def _parsetest(self, lines):
882 # We generate a shell script which outputs unique markers to line
887 # We generate a shell script which outputs unique markers to line
883 # up script results with our source. These markers include input
888 # up script results with our source. These markers include input
884 # line number and the last return code.
889 # line number and the last return code.
885 salt = "SALT" + str(time.time())
890 salt = "SALT" + str(time.time())
886 def addsalt(line, inpython):
891 def addsalt(line, inpython):
887 if inpython:
892 if inpython:
888 script.append('%s %d 0\n' % (salt, line))
893 script.append('%s %d 0\n' % (salt, line))
889 else:
894 else:
890 script.append('echo %s %s $?\n' % (salt, line))
895 script.append('echo %s %s $?\n' % (salt, line))
891
896
892 script = []
897 script = []
893
898
894 # After we run the shell script, we re-unify the script output
899 # After we run the shell script, we re-unify the script output
895 # with non-active parts of the source, with synchronization by our
900 # with non-active parts of the source, with synchronization by our
896 # SALT line number markers. The after table contains the non-active
901 # SALT line number markers. The after table contains the non-active
897 # components, ordered by line number.
902 # components, ordered by line number.
898 after = {}
903 after = {}
899
904
900 # Expected shell script output.
905 # Expected shell script output.
901 expected = {}
906 expected = {}
902
907
903 pos = prepos = -1
908 pos = prepos = -1
904
909
905 # True or False when in a true or false conditional section
910 # True or False when in a true or false conditional section
906 skipping = None
911 skipping = None
907
912
908 # We keep track of whether or not we're in a Python block so we
913 # We keep track of whether or not we're in a Python block so we
909 # can generate the surrounding doctest magic.
914 # can generate the surrounding doctest magic.
910 inpython = False
915 inpython = False
911
916
912 if self._debug:
917 if self._debug:
913 script.append('set -x\n')
918 script.append('set -x\n')
914 if os.getenv('MSYSTEM'):
919 if os.getenv('MSYSTEM'):
915 script.append('alias pwd="pwd -W"\n')
920 script.append('alias pwd="pwd -W"\n')
916
921
917 for n, l in enumerate(lines):
922 for n, l in enumerate(lines):
918 if not l.endswith('\n'):
923 if not l.endswith('\n'):
919 l += '\n'
924 l += '\n'
920 if l.startswith('#require'):
925 if l.startswith('#require'):
921 lsplit = l.split()
926 lsplit = l.split()
922 if len(lsplit) < 2 or lsplit[0] != '#require':
927 if len(lsplit) < 2 or lsplit[0] != '#require':
923 after.setdefault(pos, []).append(' !!! invalid #require\n')
928 after.setdefault(pos, []).append(' !!! invalid #require\n')
924 if not self._hghave(lsplit[1:]):
929 if not self._hghave(lsplit[1:]):
925 script = ["exit 80\n"]
930 script = ["exit 80\n"]
926 break
931 break
927 after.setdefault(pos, []).append(l)
932 after.setdefault(pos, []).append(l)
928 elif l.startswith('#if'):
933 elif l.startswith('#if'):
929 lsplit = l.split()
934 lsplit = l.split()
930 if len(lsplit) < 2 or lsplit[0] != '#if':
935 if len(lsplit) < 2 or lsplit[0] != '#if':
931 after.setdefault(pos, []).append(' !!! invalid #if\n')
936 after.setdefault(pos, []).append(' !!! invalid #if\n')
932 if skipping is not None:
937 if skipping is not None:
933 after.setdefault(pos, []).append(' !!! nested #if\n')
938 after.setdefault(pos, []).append(' !!! nested #if\n')
934 skipping = not self._hghave(lsplit[1:])
939 skipping = not self._hghave(lsplit[1:])
935 after.setdefault(pos, []).append(l)
940 after.setdefault(pos, []).append(l)
936 elif l.startswith('#else'):
941 elif l.startswith('#else'):
937 if skipping is None:
942 if skipping is None:
938 after.setdefault(pos, []).append(' !!! missing #if\n')
943 after.setdefault(pos, []).append(' !!! missing #if\n')
939 skipping = not skipping
944 skipping = not skipping
940 after.setdefault(pos, []).append(l)
945 after.setdefault(pos, []).append(l)
941 elif l.startswith('#endif'):
946 elif l.startswith('#endif'):
942 if skipping is None:
947 if skipping is None:
943 after.setdefault(pos, []).append(' !!! missing #if\n')
948 after.setdefault(pos, []).append(' !!! missing #if\n')
944 skipping = None
949 skipping = None
945 after.setdefault(pos, []).append(l)
950 after.setdefault(pos, []).append(l)
946 elif skipping:
951 elif skipping:
947 after.setdefault(pos, []).append(l)
952 after.setdefault(pos, []).append(l)
948 elif l.startswith(' >>> '): # python inlines
953 elif l.startswith(' >>> '): # python inlines
949 after.setdefault(pos, []).append(l)
954 after.setdefault(pos, []).append(l)
950 prepos = pos
955 prepos = pos
951 pos = n
956 pos = n
952 if not inpython:
957 if not inpython:
953 # We've just entered a Python block. Add the header.
958 # We've just entered a Python block. Add the header.
954 inpython = True
959 inpython = True
955 addsalt(prepos, False) # Make sure we report the exit code.
960 addsalt(prepos, False) # Make sure we report the exit code.
956 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
961 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
957 addsalt(n, True)
962 addsalt(n, True)
958 script.append(l[2:])
963 script.append(l[2:])
959 elif l.startswith(' ... '): # python inlines
964 elif l.startswith(' ... '): # python inlines
960 after.setdefault(prepos, []).append(l)
965 after.setdefault(prepos, []).append(l)
961 script.append(l[2:])
966 script.append(l[2:])
962 elif l.startswith(' $ '): # commands
967 elif l.startswith(' $ '): # commands
963 if inpython:
968 if inpython:
964 script.append('EOF\n')
969 script.append('EOF\n')
965 inpython = False
970 inpython = False
966 after.setdefault(pos, []).append(l)
971 after.setdefault(pos, []).append(l)
967 prepos = pos
972 prepos = pos
968 pos = n
973 pos = n
969 addsalt(n, False)
974 addsalt(n, False)
970 cmd = l[4:].split()
975 cmd = l[4:].split()
971 if len(cmd) == 2 and cmd[0] == 'cd':
976 if len(cmd) == 2 and cmd[0] == 'cd':
972 l = ' $ cd %s || exit 1\n' % cmd[1]
977 l = ' $ cd %s || exit 1\n' % cmd[1]
973 script.append(l[4:])
978 script.append(l[4:])
974 elif l.startswith(' > '): # continuations
979 elif l.startswith(' > '): # continuations
975 after.setdefault(prepos, []).append(l)
980 after.setdefault(prepos, []).append(l)
976 script.append(l[4:])
981 script.append(l[4:])
977 elif l.startswith(' '): # results
982 elif l.startswith(' '): # results
978 # Queue up a list of expected results.
983 # Queue up a list of expected results.
979 expected.setdefault(pos, []).append(l[2:])
984 expected.setdefault(pos, []).append(l[2:])
980 else:
985 else:
981 if inpython:
986 if inpython:
982 script.append('EOF\n')
987 script.append('EOF\n')
983 inpython = False
988 inpython = False
984 # Non-command/result. Queue up for merged output.
989 # Non-command/result. Queue up for merged output.
985 after.setdefault(pos, []).append(l)
990 after.setdefault(pos, []).append(l)
986
991
987 if inpython:
992 if inpython:
988 script.append('EOF\n')
993 script.append('EOF\n')
989 if skipping is not None:
994 if skipping is not None:
990 after.setdefault(pos, []).append(' !!! missing #endif\n')
995 after.setdefault(pos, []).append(' !!! missing #endif\n')
991 addsalt(n + 1, False)
996 addsalt(n + 1, False)
992
997
993 return salt, script, after, expected
998 return salt, script, after, expected
994
999
995 def _processoutput(self, exitcode, output, salt, after, expected):
1000 def _processoutput(self, exitcode, output, salt, after, expected):
996 # Merge the script output back into a unified test.
1001 # Merge the script output back into a unified test.
997 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1002 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
998 if exitcode != 0:
1003 if exitcode != 0:
999 warnonly = 3
1004 warnonly = 3
1000
1005
1001 pos = -1
1006 pos = -1
1002 postout = []
1007 postout = []
1003 for l in output:
1008 for l in output:
1004 lout, lcmd = l, None
1009 lout, lcmd = l, None
1005 if salt in l:
1010 if salt in l:
1006 lout, lcmd = l.split(salt, 1)
1011 lout, lcmd = l.split(salt, 1)
1007
1012
1008 if lout:
1013 if lout:
1009 if not lout.endswith('\n'):
1014 if not lout.endswith('\n'):
1010 lout += ' (no-eol)\n'
1015 lout += ' (no-eol)\n'
1011
1016
1012 # Find the expected output at the current position.
1017 # Find the expected output at the current position.
1013 el = None
1018 el = None
1014 if expected.get(pos, None):
1019 if expected.get(pos, None):
1015 el = expected[pos].pop(0)
1020 el = expected[pos].pop(0)
1016
1021
1017 r = TTest.linematch(el, lout)
1022 r = TTest.linematch(el, lout)
1018 if isinstance(r, str):
1023 if isinstance(r, str):
1019 if r == '+glob':
1024 if r == '+glob':
1020 lout = el[:-1] + ' (glob)\n'
1025 lout = el[:-1] + ' (glob)\n'
1021 r = '' # Warn only this line.
1026 r = '' # Warn only this line.
1022 elif r == '-glob':
1027 elif r == '-glob':
1023 lout = ''.join(el.rsplit(' (glob)', 1))
1028 lout = ''.join(el.rsplit(' (glob)', 1))
1024 r = '' # Warn only this line.
1029 r = '' # Warn only this line.
1025 else:
1030 else:
1026 log('\ninfo, unknown linematch result: %r\n' % r)
1031 log('\ninfo, unknown linematch result: %r\n' % r)
1027 r = False
1032 r = False
1028 if r:
1033 if r:
1029 postout.append(' ' + el)
1034 postout.append(' ' + el)
1030 else:
1035 else:
1031 if self.NEEDESCAPE(lout):
1036 if self.NEEDESCAPE(lout):
1032 lout = TTest._stringescape('%s (esc)\n' %
1037 lout = TTest._stringescape('%s (esc)\n' %
1033 lout.rstrip('\n'))
1038 lout.rstrip('\n'))
1034 postout.append(' ' + lout) # Let diff deal with it.
1039 postout.append(' ' + lout) # Let diff deal with it.
1035 if r != '': # If line failed.
1040 if r != '': # If line failed.
1036 warnonly = 3 # for sure not
1041 warnonly = 3 # for sure not
1037 elif warnonly == 1: # Is "not yet" and line is warn only.
1042 elif warnonly == 1: # Is "not yet" and line is warn only.
1038 warnonly = 2 # Yes do warn.
1043 warnonly = 2 # Yes do warn.
1039
1044
1040 if lcmd:
1045 if lcmd:
1041 # Add on last return code.
1046 # Add on last return code.
1042 ret = int(lcmd.split()[1])
1047 ret = int(lcmd.split()[1])
1043 if ret != 0:
1048 if ret != 0:
1044 postout.append(' [%s]\n' % ret)
1049 postout.append(' [%s]\n' % ret)
1045 if pos in after:
1050 if pos in after:
1046 # Merge in non-active test bits.
1051 # Merge in non-active test bits.
1047 postout += after.pop(pos)
1052 postout += after.pop(pos)
1048 pos = int(lcmd.split()[0])
1053 pos = int(lcmd.split()[0])
1049
1054
1050 if pos in after:
1055 if pos in after:
1051 postout += after.pop(pos)
1056 postout += after.pop(pos)
1052
1057
1053 if warnonly == 2:
1058 if warnonly == 2:
1054 exitcode = False # Set exitcode to warned.
1059 exitcode = False # Set exitcode to warned.
1055
1060
1056 return exitcode, postout
1061 return exitcode, postout
1057
1062
1058 @staticmethod
1063 @staticmethod
1059 def rematch(el, l):
1064 def rematch(el, l):
1060 try:
1065 try:
1061 # use \Z to ensure that the regex matches to the end of the string
1066 # use \Z to ensure that the regex matches to the end of the string
1062 if os.name == 'nt':
1067 if os.name == 'nt':
1063 return re.match(el + r'\r?\n\Z', l)
1068 return re.match(el + r'\r?\n\Z', l)
1064 return re.match(el + r'\n\Z', l)
1069 return re.match(el + r'\n\Z', l)
1065 except re.error:
1070 except re.error:
1066 # el is an invalid regex
1071 # el is an invalid regex
1067 return False
1072 return False
1068
1073
1069 @staticmethod
1074 @staticmethod
1070 def globmatch(el, l):
1075 def globmatch(el, l):
1071 # The only supported special characters are * and ? plus / which also
1076 # The only supported special characters are * and ? plus / which also
1072 # matches \ on windows. Escaping of these characters is supported.
1077 # matches \ on windows. Escaping of these characters is supported.
1073 if el + '\n' == l:
1078 if el + '\n' == l:
1074 if os.altsep:
1079 if os.altsep:
1075 # matching on "/" is not needed for this line
1080 # matching on "/" is not needed for this line
1076 for pat in checkcodeglobpats:
1081 for pat in checkcodeglobpats:
1077 if pat.match(el):
1082 if pat.match(el):
1078 return True
1083 return True
1079 return '-glob'
1084 return '-glob'
1080 return True
1085 return True
1081 i, n = 0, len(el)
1086 i, n = 0, len(el)
1082 res = ''
1087 res = ''
1083 while i < n:
1088 while i < n:
1084 c = el[i]
1089 c = el[i]
1085 i += 1
1090 i += 1
1086 if c == '\\' and i < n and el[i] in '*?\\/':
1091 if c == '\\' and i < n and el[i] in '*?\\/':
1087 res += el[i - 1:i + 1]
1092 res += el[i - 1:i + 1]
1088 i += 1
1093 i += 1
1089 elif c == '*':
1094 elif c == '*':
1090 res += '.*'
1095 res += '.*'
1091 elif c == '?':
1096 elif c == '?':
1092 res += '.'
1097 res += '.'
1093 elif c == '/' and os.altsep:
1098 elif c == '/' and os.altsep:
1094 res += '[/\\\\]'
1099 res += '[/\\\\]'
1095 else:
1100 else:
1096 res += re.escape(c)
1101 res += re.escape(c)
1097 return TTest.rematch(res, l)
1102 return TTest.rematch(res, l)
1098
1103
1099 @staticmethod
1104 @staticmethod
1100 def linematch(el, l):
1105 def linematch(el, l):
1101 if el == l: # perfect match (fast)
1106 if el == l: # perfect match (fast)
1102 return True
1107 return True
1103 if el:
1108 if el:
1104 if el.endswith(" (esc)\n"):
1109 if el.endswith(" (esc)\n"):
1105 el = el[:-7].decode('string-escape') + '\n'
1110 el = el[:-7].decode('string-escape') + '\n'
1106 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1111 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1107 return True
1112 return True
1108 if el.endswith(" (re)\n"):
1113 if el.endswith(" (re)\n"):
1109 return TTest.rematch(el[:-6], l)
1114 return TTest.rematch(el[:-6], l)
1110 if el.endswith(" (glob)\n"):
1115 if el.endswith(" (glob)\n"):
1111 # ignore '(glob)' added to l by 'replacements'
1116 # ignore '(glob)' added to l by 'replacements'
1112 if l.endswith(" (glob)\n"):
1117 if l.endswith(" (glob)\n"):
1113 l = l[:-8] + "\n"
1118 l = l[:-8] + "\n"
1114 return TTest.globmatch(el[:-8], l)
1119 return TTest.globmatch(el[:-8], l)
1115 if os.altsep and l.replace('\\', '/') == el:
1120 if os.altsep and l.replace('\\', '/') == el:
1116 return '+glob'
1121 return '+glob'
1117 return False
1122 return False
1118
1123
1119 @staticmethod
1124 @staticmethod
1120 def parsehghaveoutput(lines):
1125 def parsehghaveoutput(lines):
1121 '''Parse hghave log lines.
1126 '''Parse hghave log lines.
1122
1127
1123 Return tuple of lists (missing, failed):
1128 Return tuple of lists (missing, failed):
1124 * the missing/unknown features
1129 * the missing/unknown features
1125 * the features for which existence check failed'''
1130 * the features for which existence check failed'''
1126 missing = []
1131 missing = []
1127 failed = []
1132 failed = []
1128 for line in lines:
1133 for line in lines:
1129 if line.startswith(TTest.SKIPPED_PREFIX):
1134 if line.startswith(TTest.SKIPPED_PREFIX):
1130 line = line.splitlines()[0]
1135 line = line.splitlines()[0]
1131 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1136 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1132 elif line.startswith(TTest.FAILED_PREFIX):
1137 elif line.startswith(TTest.FAILED_PREFIX):
1133 line = line.splitlines()[0]
1138 line = line.splitlines()[0]
1134 failed.append(line[len(TTest.FAILED_PREFIX):])
1139 failed.append(line[len(TTest.FAILED_PREFIX):])
1135
1140
1136 return missing, failed
1141 return missing, failed
1137
1142
1138 @staticmethod
1143 @staticmethod
1139 def _escapef(m):
1144 def _escapef(m):
1140 return TTest.ESCAPEMAP[m.group(0)]
1145 return TTest.ESCAPEMAP[m.group(0)]
1141
1146
1142 @staticmethod
1147 @staticmethod
1143 def _stringescape(s):
1148 def _stringescape(s):
1144 return TTest.ESCAPESUB(TTest._escapef, s)
1149 return TTest.ESCAPESUB(TTest._escapef, s)
1145
1150
1146 iolock = threading.RLock()
1151 iolock = threading.RLock()
1147
1152
1148 class SkipTest(Exception):
1153 class SkipTest(Exception):
1149 """Raised to indicate that a test is to be skipped."""
1154 """Raised to indicate that a test is to be skipped."""
1150
1155
1151 class IgnoreTest(Exception):
1156 class IgnoreTest(Exception):
1152 """Raised to indicate that a test is to be ignored."""
1157 """Raised to indicate that a test is to be ignored."""
1153
1158
1154 class WarnTest(Exception):
1159 class WarnTest(Exception):
1155 """Raised to indicate that a test warned."""
1160 """Raised to indicate that a test warned."""
1156
1161
1157 class TestResult(unittest._TextTestResult):
1162 class TestResult(unittest._TextTestResult):
1158 """Holds results when executing via unittest."""
1163 """Holds results when executing via unittest."""
1159 # Don't worry too much about accessing the non-public _TextTestResult.
1164 # Don't worry too much about accessing the non-public _TextTestResult.
1160 # It is relatively common in Python testing tools.
1165 # It is relatively common in Python testing tools.
1161 def __init__(self, options, *args, **kwargs):
1166 def __init__(self, options, *args, **kwargs):
1162 super(TestResult, self).__init__(*args, **kwargs)
1167 super(TestResult, self).__init__(*args, **kwargs)
1163
1168
1164 self._options = options
1169 self._options = options
1165
1170
1166 # unittest.TestResult didn't have skipped until 2.7. We need to
1171 # unittest.TestResult didn't have skipped until 2.7. We need to
1167 # polyfill it.
1172 # polyfill it.
1168 self.skipped = []
1173 self.skipped = []
1169
1174
1170 # We have a custom "ignored" result that isn't present in any Python
1175 # We have a custom "ignored" result that isn't present in any Python
1171 # unittest implementation. It is very similar to skipped. It may make
1176 # unittest implementation. It is very similar to skipped. It may make
1172 # sense to map it into skip some day.
1177 # sense to map it into skip some day.
1173 self.ignored = []
1178 self.ignored = []
1174
1179
1175 # We have a custom "warned" result that isn't present in any Python
1180 # We have a custom "warned" result that isn't present in any Python
1176 # unittest implementation. It is very similar to failed. It may make
1181 # unittest implementation. It is very similar to failed. It may make
1177 # sense to map it into fail some day.
1182 # sense to map it into fail some day.
1178 self.warned = []
1183 self.warned = []
1179
1184
1180 self.times = []
1185 self.times = []
1181 # Data stored for the benefit of generating xunit reports.
1186 # Data stored for the benefit of generating xunit reports.
1182 self.successes = []
1187 self.successes = []
1183 self.faildata = {}
1188 self.faildata = {}
1184
1189
1185 def addFailure(self, test, reason):
1190 def addFailure(self, test, reason):
1186 self.failures.append((test, reason))
1191 self.failures.append((test, reason))
1187
1192
1188 if self._options.first:
1193 if self._options.first:
1189 self.stop()
1194 self.stop()
1190 else:
1195 else:
1191 iolock.acquire()
1196 iolock.acquire()
1192 if not self._options.nodiff:
1197 if not self._options.nodiff:
1193 self.stream.write('\nERROR: %s output changed\n' % test)
1198 self.stream.write('\nERROR: %s output changed\n' % test)
1194
1199
1195 self.stream.write('!')
1200 self.stream.write('!')
1196 self.stream.flush()
1201 self.stream.flush()
1197 iolock.release()
1202 iolock.release()
1198
1203
1199 def addSuccess(self, test):
1204 def addSuccess(self, test):
1200 iolock.acquire()
1205 iolock.acquire()
1201 super(TestResult, self).addSuccess(test)
1206 super(TestResult, self).addSuccess(test)
1202 iolock.release()
1207 iolock.release()
1203 self.successes.append(test)
1208 self.successes.append(test)
1204
1209
1205 def addError(self, test, err):
1210 def addError(self, test, err):
1206 super(TestResult, self).addError(test, err)
1211 super(TestResult, self).addError(test, err)
1207 if self._options.first:
1212 if self._options.first:
1208 self.stop()
1213 self.stop()
1209
1214
1210 # Polyfill.
1215 # Polyfill.
1211 def addSkip(self, test, reason):
1216 def addSkip(self, test, reason):
1212 self.skipped.append((test, reason))
1217 self.skipped.append((test, reason))
1213 iolock.acquire()
1218 iolock.acquire()
1214 if self.showAll:
1219 if self.showAll:
1215 self.stream.writeln('skipped %s' % reason)
1220 self.stream.writeln('skipped %s' % reason)
1216 else:
1221 else:
1217 self.stream.write('s')
1222 self.stream.write('s')
1218 self.stream.flush()
1223 self.stream.flush()
1219 iolock.release()
1224 iolock.release()
1220
1225
1221 def addIgnore(self, test, reason):
1226 def addIgnore(self, test, reason):
1222 self.ignored.append((test, reason))
1227 self.ignored.append((test, reason))
1223 iolock.acquire()
1228 iolock.acquire()
1224 if self.showAll:
1229 if self.showAll:
1225 self.stream.writeln('ignored %s' % reason)
1230 self.stream.writeln('ignored %s' % reason)
1226 else:
1231 else:
1227 if reason != 'not retesting' and reason != "doesn't match keyword":
1232 if reason != 'not retesting' and reason != "doesn't match keyword":
1228 self.stream.write('i')
1233 self.stream.write('i')
1229 else:
1234 else:
1230 self.testsRun += 1
1235 self.testsRun += 1
1231 self.stream.flush()
1236 self.stream.flush()
1232 iolock.release()
1237 iolock.release()
1233
1238
1234 def addWarn(self, test, reason):
1239 def addWarn(self, test, reason):
1235 self.warned.append((test, reason))
1240 self.warned.append((test, reason))
1236
1241
1237 if self._options.first:
1242 if self._options.first:
1238 self.stop()
1243 self.stop()
1239
1244
1240 iolock.acquire()
1245 iolock.acquire()
1241 if self.showAll:
1246 if self.showAll:
1242 self.stream.writeln('warned %s' % reason)
1247 self.stream.writeln('warned %s' % reason)
1243 else:
1248 else:
1244 self.stream.write('~')
1249 self.stream.write('~')
1245 self.stream.flush()
1250 self.stream.flush()
1246 iolock.release()
1251 iolock.release()
1247
1252
1248 def addOutputMismatch(self, test, ret, got, expected):
1253 def addOutputMismatch(self, test, ret, got, expected):
1249 """Record a mismatch in test output for a particular test."""
1254 """Record a mismatch in test output for a particular test."""
1250 if self.shouldStop:
1255 if self.shouldStop:
1251 # don't print, some other test case already failed and
1256 # don't print, some other test case already failed and
1252 # printed, we're just stale and probably failed due to our
1257 # printed, we're just stale and probably failed due to our
1253 # temp dir getting cleaned up.
1258 # temp dir getting cleaned up.
1254 return
1259 return
1255
1260
1256 accepted = False
1261 accepted = False
1257 failed = False
1262 failed = False
1258 lines = []
1263 lines = []
1259
1264
1260 iolock.acquire()
1265 iolock.acquire()
1261 if self._options.nodiff:
1266 if self._options.nodiff:
1262 pass
1267 pass
1263 elif self._options.view:
1268 elif self._options.view:
1264 os.system("%s %s %s" %
1269 os.system("%s %s %s" %
1265 (self._options.view, test.refpath, test.errpath))
1270 (self._options.view, test.refpath, test.errpath))
1266 else:
1271 else:
1267 servefail, lines = getdiff(expected, got,
1272 servefail, lines = getdiff(expected, got,
1268 test.refpath, test.errpath)
1273 test.refpath, test.errpath)
1269 if servefail:
1274 if servefail:
1270 self.addFailure(
1275 self.addFailure(
1271 test,
1276 test,
1272 'server failed to start (HGPORT=%s)' % test._startport)
1277 'server failed to start (HGPORT=%s)' % test._startport)
1273 else:
1278 else:
1274 self.stream.write('\n')
1279 self.stream.write('\n')
1275 for line in lines:
1280 for line in lines:
1276 self.stream.write(line)
1281 self.stream.write(line)
1277 self.stream.flush()
1282 self.stream.flush()
1278
1283
1279 # handle interactive prompt without releasing iolock
1284 # handle interactive prompt without releasing iolock
1280 if self._options.interactive:
1285 if self._options.interactive:
1281 self.stream.write('Accept this change? [n] ')
1286 self.stream.write('Accept this change? [n] ')
1282 answer = sys.stdin.readline().strip()
1287 answer = sys.stdin.readline().strip()
1283 if answer.lower() in ('y', 'yes'):
1288 if answer.lower() in ('y', 'yes'):
1284 if test.name.endswith('.t'):
1289 if test.name.endswith('.t'):
1285 rename(test.errpath, test.path)
1290 rename(test.errpath, test.path)
1286 else:
1291 else:
1287 rename(test.errpath, '%s.out' % test.path)
1292 rename(test.errpath, '%s.out' % test.path)
1288 accepted = True
1293 accepted = True
1289 if not accepted and not failed:
1294 if not accepted and not failed:
1290 self.faildata[test.name] = ''.join(lines)
1295 self.faildata[test.name] = ''.join(lines)
1291 iolock.release()
1296 iolock.release()
1292
1297
1293 return accepted
1298 return accepted
1294
1299
1295 def startTest(self, test):
1300 def startTest(self, test):
1296 super(TestResult, self).startTest(test)
1301 super(TestResult, self).startTest(test)
1297
1302
1298 # os.times module computes the user time and system time spent by
1303 # os.times module computes the user time and system time spent by
1299 # child's processes along with real elapsed time taken by a process.
1304 # child's processes along with real elapsed time taken by a process.
1300 # This module has one limitation. It can only work for Linux user
1305 # This module has one limitation. It can only work for Linux user
1301 # and not for Windows.
1306 # and not for Windows.
1302 test.started = os.times()
1307 test.started = os.times()
1303
1308
1304 def stopTest(self, test, interrupted=False):
1309 def stopTest(self, test, interrupted=False):
1305 super(TestResult, self).stopTest(test)
1310 super(TestResult, self).stopTest(test)
1306
1311
1307 test.stopped = os.times()
1312 test.stopped = os.times()
1308
1313
1309 starttime = test.started
1314 starttime = test.started
1310 endtime = test.stopped
1315 endtime = test.stopped
1311 self.times.append((test.name,
1316 self.times.append((test.name,
1312 endtime[2] - starttime[2], # user space CPU time
1317 endtime[2] - starttime[2], # user space CPU time
1313 endtime[3] - starttime[3], # sys space CPU time
1318 endtime[3] - starttime[3], # sys space CPU time
1314 endtime[4] - starttime[4], # real time
1319 endtime[4] - starttime[4], # real time
1315 ))
1320 ))
1316
1321
1317 if interrupted:
1322 if interrupted:
1318 iolock.acquire()
1323 iolock.acquire()
1319 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1324 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1320 test.name, self.times[-1][3]))
1325 test.name, self.times[-1][3]))
1321 iolock.release()
1326 iolock.release()
1322
1327
1323 class TestSuite(unittest.TestSuite):
1328 class TestSuite(unittest.TestSuite):
1324 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1329 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1325
1330
1326 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1331 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1327 retest=False, keywords=None, loop=False, runs_per_test=1,
1332 retest=False, keywords=None, loop=False, runs_per_test=1,
1328 loadtest=None,
1333 loadtest=None,
1329 *args, **kwargs):
1334 *args, **kwargs):
1330 """Create a new instance that can run tests with a configuration.
1335 """Create a new instance that can run tests with a configuration.
1331
1336
1332 testdir specifies the directory where tests are executed from. This
1337 testdir specifies the directory where tests are executed from. This
1333 is typically the ``tests`` directory from Mercurial's source
1338 is typically the ``tests`` directory from Mercurial's source
1334 repository.
1339 repository.
1335
1340
1336 jobs specifies the number of jobs to run concurrently. Each test
1341 jobs specifies the number of jobs to run concurrently. Each test
1337 executes on its own thread. Tests actually spawn new processes, so
1342 executes on its own thread. Tests actually spawn new processes, so
1338 state mutation should not be an issue.
1343 state mutation should not be an issue.
1339
1344
1340 whitelist and blacklist denote tests that have been whitelisted and
1345 whitelist and blacklist denote tests that have been whitelisted and
1341 blacklisted, respectively. These arguments don't belong in TestSuite.
1346 blacklisted, respectively. These arguments don't belong in TestSuite.
1342 Instead, whitelist and blacklist should be handled by the thing that
1347 Instead, whitelist and blacklist should be handled by the thing that
1343 populates the TestSuite with tests. They are present to preserve
1348 populates the TestSuite with tests. They are present to preserve
1344 backwards compatible behavior which reports skipped tests as part
1349 backwards compatible behavior which reports skipped tests as part
1345 of the results.
1350 of the results.
1346
1351
1347 retest denotes whether to retest failed tests. This arguably belongs
1352 retest denotes whether to retest failed tests. This arguably belongs
1348 outside of TestSuite.
1353 outside of TestSuite.
1349
1354
1350 keywords denotes key words that will be used to filter which tests
1355 keywords denotes key words that will be used to filter which tests
1351 to execute. This arguably belongs outside of TestSuite.
1356 to execute. This arguably belongs outside of TestSuite.
1352
1357
1353 loop denotes whether to loop over tests forever.
1358 loop denotes whether to loop over tests forever.
1354 """
1359 """
1355 super(TestSuite, self).__init__(*args, **kwargs)
1360 super(TestSuite, self).__init__(*args, **kwargs)
1356
1361
1357 self._jobs = jobs
1362 self._jobs = jobs
1358 self._whitelist = whitelist
1363 self._whitelist = whitelist
1359 self._blacklist = blacklist
1364 self._blacklist = blacklist
1360 self._retest = retest
1365 self._retest = retest
1361 self._keywords = keywords
1366 self._keywords = keywords
1362 self._loop = loop
1367 self._loop = loop
1363 self._runs_per_test = runs_per_test
1368 self._runs_per_test = runs_per_test
1364 self._loadtest = loadtest
1369 self._loadtest = loadtest
1365
1370
1366 def run(self, result):
1371 def run(self, result):
1367 # We have a number of filters that need to be applied. We do this
1372 # We have a number of filters that need to be applied. We do this
1368 # here instead of inside Test because it makes the running logic for
1373 # here instead of inside Test because it makes the running logic for
1369 # Test simpler.
1374 # Test simpler.
1370 tests = []
1375 tests = []
1371 num_tests = [0]
1376 num_tests = [0]
1372 for test in self._tests:
1377 for test in self._tests:
1373 def get():
1378 def get():
1374 num_tests[0] += 1
1379 num_tests[0] += 1
1375 if getattr(test, 'should_reload', False):
1380 if getattr(test, 'should_reload', False):
1376 return self._loadtest(test.name, num_tests[0])
1381 return self._loadtest(test.name, num_tests[0])
1377 return test
1382 return test
1378 if not os.path.exists(test.path):
1383 if not os.path.exists(test.path):
1379 result.addSkip(test, "Doesn't exist")
1384 result.addSkip(test, "Doesn't exist")
1380 continue
1385 continue
1381
1386
1382 if not (self._whitelist and test.name in self._whitelist):
1387 if not (self._whitelist and test.name in self._whitelist):
1383 if self._blacklist and test.name in self._blacklist:
1388 if self._blacklist and test.name in self._blacklist:
1384 result.addSkip(test, 'blacklisted')
1389 result.addSkip(test, 'blacklisted')
1385 continue
1390 continue
1386
1391
1387 if self._retest and not os.path.exists(test.errpath):
1392 if self._retest and not os.path.exists(test.errpath):
1388 result.addIgnore(test, 'not retesting')
1393 result.addIgnore(test, 'not retesting')
1389 continue
1394 continue
1390
1395
1391 if self._keywords:
1396 if self._keywords:
1392 f = open(test.path, 'rb')
1397 f = open(test.path, 'rb')
1393 t = f.read().lower() + test.name.lower()
1398 t = f.read().lower() + test.name.lower()
1394 f.close()
1399 f.close()
1395 ignored = False
1400 ignored = False
1396 for k in self._keywords.lower().split():
1401 for k in self._keywords.lower().split():
1397 if k not in t:
1402 if k not in t:
1398 result.addIgnore(test, "doesn't match keyword")
1403 result.addIgnore(test, "doesn't match keyword")
1399 ignored = True
1404 ignored = True
1400 break
1405 break
1401
1406
1402 if ignored:
1407 if ignored:
1403 continue
1408 continue
1404 for _ in xrange(self._runs_per_test):
1409 for _ in xrange(self._runs_per_test):
1405 tests.append(get())
1410 tests.append(get())
1406
1411
1407 runtests = list(tests)
1412 runtests = list(tests)
1408 done = queue.Queue()
1413 done = queue.Queue()
1409 running = 0
1414 running = 0
1410
1415
1411 def job(test, result):
1416 def job(test, result):
1412 try:
1417 try:
1413 test(result)
1418 test(result)
1414 done.put(None)
1419 done.put(None)
1415 except KeyboardInterrupt:
1420 except KeyboardInterrupt:
1416 pass
1421 pass
1417 except: # re-raises
1422 except: # re-raises
1418 done.put(('!', test, 'run-test raised an error, see traceback'))
1423 done.put(('!', test, 'run-test raised an error, see traceback'))
1419 raise
1424 raise
1420
1425
1421 stoppedearly = False
1426 stoppedearly = False
1422
1427
1423 try:
1428 try:
1424 while tests or running:
1429 while tests or running:
1425 if not done.empty() or running == self._jobs or not tests:
1430 if not done.empty() or running == self._jobs or not tests:
1426 try:
1431 try:
1427 done.get(True, 1)
1432 done.get(True, 1)
1428 running -= 1
1433 running -= 1
1429 if result and result.shouldStop:
1434 if result and result.shouldStop:
1430 stoppedearly = True
1435 stoppedearly = True
1431 break
1436 break
1432 except queue.Empty:
1437 except queue.Empty:
1433 continue
1438 continue
1434 if tests and not running == self._jobs:
1439 if tests and not running == self._jobs:
1435 test = tests.pop(0)
1440 test = tests.pop(0)
1436 if self._loop:
1441 if self._loop:
1437 if getattr(test, 'should_reload', False):
1442 if getattr(test, 'should_reload', False):
1438 num_tests[0] += 1
1443 num_tests[0] += 1
1439 tests.append(
1444 tests.append(
1440 self._loadtest(test.name, num_tests[0]))
1445 self._loadtest(test.name, num_tests[0]))
1441 else:
1446 else:
1442 tests.append(test)
1447 tests.append(test)
1443 t = threading.Thread(target=job, name=test.name,
1448 t = threading.Thread(target=job, name=test.name,
1444 args=(test, result))
1449 args=(test, result))
1445 t.start()
1450 t.start()
1446 running += 1
1451 running += 1
1447
1452
1448 # If we stop early we still need to wait on started tests to
1453 # If we stop early we still need to wait on started tests to
1449 # finish. Otherwise, there is a race between the test completing
1454 # finish. Otherwise, there is a race between the test completing
1450 # and the test's cleanup code running. This could result in the
1455 # and the test's cleanup code running. This could result in the
1451 # test reporting incorrect.
1456 # test reporting incorrect.
1452 if stoppedearly:
1457 if stoppedearly:
1453 while running:
1458 while running:
1454 try:
1459 try:
1455 done.get(True, 1)
1460 done.get(True, 1)
1456 running -= 1
1461 running -= 1
1457 except queue.Empty:
1462 except queue.Empty:
1458 continue
1463 continue
1459 except KeyboardInterrupt:
1464 except KeyboardInterrupt:
1460 for test in runtests:
1465 for test in runtests:
1461 test.abort()
1466 test.abort()
1462
1467
1463 return result
1468 return result
1464
1469
1465 class TextTestRunner(unittest.TextTestRunner):
1470 class TextTestRunner(unittest.TextTestRunner):
1466 """Custom unittest test runner that uses appropriate settings."""
1471 """Custom unittest test runner that uses appropriate settings."""
1467
1472
1468 def __init__(self, runner, *args, **kwargs):
1473 def __init__(self, runner, *args, **kwargs):
1469 super(TextTestRunner, self).__init__(*args, **kwargs)
1474 super(TextTestRunner, self).__init__(*args, **kwargs)
1470
1475
1471 self._runner = runner
1476 self._runner = runner
1472
1477
1473 def run(self, test):
1478 def run(self, test):
1474 result = TestResult(self._runner.options, self.stream,
1479 result = TestResult(self._runner.options, self.stream,
1475 self.descriptions, self.verbosity)
1480 self.descriptions, self.verbosity)
1476
1481
1477 test(result)
1482 test(result)
1478
1483
1479 failed = len(result.failures)
1484 failed = len(result.failures)
1480 warned = len(result.warned)
1485 warned = len(result.warned)
1481 skipped = len(result.skipped)
1486 skipped = len(result.skipped)
1482 ignored = len(result.ignored)
1487 ignored = len(result.ignored)
1483
1488
1484 iolock.acquire()
1489 iolock.acquire()
1485 self.stream.writeln('')
1490 self.stream.writeln('')
1486
1491
1487 if not self._runner.options.noskips:
1492 if not self._runner.options.noskips:
1488 for test, msg in result.skipped:
1493 for test, msg in result.skipped:
1489 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1494 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1490 for test, msg in result.warned:
1495 for test, msg in result.warned:
1491 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1496 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1492 for test, msg in result.failures:
1497 for test, msg in result.failures:
1493 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1498 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1494 for test, msg in result.errors:
1499 for test, msg in result.errors:
1495 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1500 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1496
1501
1497 if self._runner.options.xunit:
1502 if self._runner.options.xunit:
1498 xuf = open(self._runner.options.xunit, 'wb')
1503 xuf = open(self._runner.options.xunit, 'wb')
1499 try:
1504 try:
1500 timesd = dict((t[0], t[3]) for t in result.times)
1505 timesd = dict((t[0], t[3]) for t in result.times)
1501 doc = minidom.Document()
1506 doc = minidom.Document()
1502 s = doc.createElement('testsuite')
1507 s = doc.createElement('testsuite')
1503 s.setAttribute('name', 'run-tests')
1508 s.setAttribute('name', 'run-tests')
1504 s.setAttribute('tests', str(result.testsRun))
1509 s.setAttribute('tests', str(result.testsRun))
1505 s.setAttribute('errors', "0") # TODO
1510 s.setAttribute('errors', "0") # TODO
1506 s.setAttribute('failures', str(failed))
1511 s.setAttribute('failures', str(failed))
1507 s.setAttribute('skipped', str(skipped + ignored))
1512 s.setAttribute('skipped', str(skipped + ignored))
1508 doc.appendChild(s)
1513 doc.appendChild(s)
1509 for tc in result.successes:
1514 for tc in result.successes:
1510 t = doc.createElement('testcase')
1515 t = doc.createElement('testcase')
1511 t.setAttribute('name', tc.name)
1516 t.setAttribute('name', tc.name)
1512 t.setAttribute('time', '%.3f' % timesd[tc.name])
1517 t.setAttribute('time', '%.3f' % timesd[tc.name])
1513 s.appendChild(t)
1518 s.appendChild(t)
1514 for tc, err in sorted(result.faildata.iteritems()):
1519 for tc, err in sorted(result.faildata.iteritems()):
1515 t = doc.createElement('testcase')
1520 t = doc.createElement('testcase')
1516 t.setAttribute('name', tc)
1521 t.setAttribute('name', tc)
1517 t.setAttribute('time', '%.3f' % timesd[tc])
1522 t.setAttribute('time', '%.3f' % timesd[tc])
1518 # createCDATASection expects a unicode or it will convert
1523 # createCDATASection expects a unicode or it will convert
1519 # using default conversion rules, which will fail if
1524 # using default conversion rules, which will fail if
1520 # string isn't ASCII.
1525 # string isn't ASCII.
1521 err = cdatasafe(err).decode('utf-8', 'replace')
1526 err = cdatasafe(err).decode('utf-8', 'replace')
1522 cd = doc.createCDATASection(err)
1527 cd = doc.createCDATASection(err)
1523 t.appendChild(cd)
1528 t.appendChild(cd)
1524 s.appendChild(t)
1529 s.appendChild(t)
1525 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1530 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1526 finally:
1531 finally:
1527 xuf.close()
1532 xuf.close()
1528
1533
1529 if self._runner.options.json:
1534 if self._runner.options.json:
1530 if json is None:
1535 if json is None:
1531 raise ImportError("json module not installed")
1536 raise ImportError("json module not installed")
1532 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1537 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1533 fp = open(jsonpath, 'w')
1538 fp = open(jsonpath, 'w')
1534 try:
1539 try:
1535 timesd = {}
1540 timesd = {}
1536 for tdata in result.times:
1541 for tdata in result.times:
1537 test = tdata[0]
1542 test = tdata[0]
1538 timesd[test] = tdata[1:]
1543 timesd[test] = tdata[1:]
1539
1544
1540 outcome = {}
1545 outcome = {}
1541 groups = [('success', ((tc, None) for tc in result.successes)),
1546 groups = [('success', ((tc, None) for tc in result.successes)),
1542 ('failure', result.failures),
1547 ('failure', result.failures),
1543 ('skip', result.skipped)]
1548 ('skip', result.skipped)]
1544 for res, testcases in groups:
1549 for res, testcases in groups:
1545 for tc, __ in testcases:
1550 for tc, __ in testcases:
1546 testresult = {'result': res,
1551 testresult = {'result': res,
1547 'time': ('%0.3f' % timesd[tc.name][2]),
1552 'time': ('%0.3f' % timesd[tc.name][2]),
1548 'cuser': ('%0.3f' % timesd[tc.name][0]),
1553 'cuser': ('%0.3f' % timesd[tc.name][0]),
1549 'csys': ('%0.3f' % timesd[tc.name][1])}
1554 'csys': ('%0.3f' % timesd[tc.name][1])}
1550 outcome[tc.name] = testresult
1555 outcome[tc.name] = testresult
1551
1556
1552 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1557 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1553 fp.writelines(("testreport =", jsonout))
1558 fp.writelines(("testreport =", jsonout))
1554 finally:
1559 finally:
1555 fp.close()
1560 fp.close()
1556
1561
1557 self._runner._checkhglib('Tested')
1562 self._runner._checkhglib('Tested')
1558
1563
1559 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1564 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1560 % (result.testsRun,
1565 % (result.testsRun,
1561 skipped + ignored, warned, failed))
1566 skipped + ignored, warned, failed))
1562 if failed:
1567 if failed:
1563 self.stream.writeln('python hash seed: %s' %
1568 self.stream.writeln('python hash seed: %s' %
1564 os.environ['PYTHONHASHSEED'])
1569 os.environ['PYTHONHASHSEED'])
1565 if self._runner.options.time:
1570 if self._runner.options.time:
1566 self.printtimes(result.times)
1571 self.printtimes(result.times)
1567
1572
1568 iolock.release()
1573 iolock.release()
1569
1574
1570 return result
1575 return result
1571
1576
1572 def printtimes(self, times):
1577 def printtimes(self, times):
1573 # iolock held by run
1578 # iolock held by run
1574 self.stream.writeln('# Producing time report')
1579 self.stream.writeln('# Producing time report')
1575 times.sort(key=lambda t: (t[3]))
1580 times.sort(key=lambda t: (t[3]))
1576 cols = '%7.3f %7.3f %7.3f %s'
1581 cols = '%7.3f %7.3f %7.3f %s'
1577 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1582 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1578 'Test'))
1583 'Test'))
1579 for tdata in times:
1584 for tdata in times:
1580 test = tdata[0]
1585 test = tdata[0]
1581 cuser, csys, real = tdata[1:4]
1586 cuser, csys, real = tdata[1:4]
1582 self.stream.writeln(cols % (cuser, csys, real, test))
1587 self.stream.writeln(cols % (cuser, csys, real, test))
1583
1588
1584 class TestRunner(object):
1589 class TestRunner(object):
1585 """Holds context for executing tests.
1590 """Holds context for executing tests.
1586
1591
1587 Tests rely on a lot of state. This object holds it for them.
1592 Tests rely on a lot of state. This object holds it for them.
1588 """
1593 """
1589
1594
1590 # Programs required to run tests.
1595 # Programs required to run tests.
1591 REQUIREDTOOLS = [
1596 REQUIREDTOOLS = [
1592 os.path.basename(sys.executable),
1597 os.path.basename(sys.executable),
1593 'diff',
1598 'diff',
1594 'grep',
1599 'grep',
1595 'unzip',
1600 'unzip',
1596 'gunzip',
1601 'gunzip',
1597 'bunzip2',
1602 'bunzip2',
1598 'sed',
1603 'sed',
1599 ]
1604 ]
1600
1605
1601 # Maps file extensions to test class.
1606 # Maps file extensions to test class.
1602 TESTTYPES = [
1607 TESTTYPES = [
1603 ('.py', PythonTest),
1608 ('.py', PythonTest),
1604 ('.t', TTest),
1609 ('.t', TTest),
1605 ]
1610 ]
1606
1611
1607 def __init__(self):
1612 def __init__(self):
1608 self.options = None
1613 self.options = None
1609 self._hgroot = None
1614 self._hgroot = None
1610 self._testdir = None
1615 self._testdir = None
1611 self._hgtmp = None
1616 self._hgtmp = None
1612 self._installdir = None
1617 self._installdir = None
1613 self._bindir = None
1618 self._bindir = None
1614 self._tmpbinddir = None
1619 self._tmpbinddir = None
1615 self._pythondir = None
1620 self._pythondir = None
1616 self._coveragefile = None
1621 self._coveragefile = None
1617 self._createdfiles = []
1622 self._createdfiles = []
1618 self._hgpath = None
1623 self._hgpath = None
1619 self._portoffset = 0
1624 self._portoffset = 0
1620 self._ports = {}
1625 self._ports = {}
1621
1626
1622 def run(self, args, parser=None):
1627 def run(self, args, parser=None):
1623 """Run the test suite."""
1628 """Run the test suite."""
1624 oldmask = os.umask(022)
1629 oldmask = os.umask(0o22)
1625 try:
1630 try:
1626 parser = parser or getparser()
1631 parser = parser or getparser()
1627 options, args = parseargs(args, parser)
1632 options, args = parseargs(args, parser)
1628 self.options = options
1633 self.options = options
1629
1634
1630 self._checktools()
1635 self._checktools()
1631 tests = self.findtests(args)
1636 tests = self.findtests(args)
1632 return self._run(tests)
1637 return self._run(tests)
1633 finally:
1638 finally:
1634 os.umask(oldmask)
1639 os.umask(oldmask)
1635
1640
1636 def _run(self, tests):
1641 def _run(self, tests):
1637 if self.options.random:
1642 if self.options.random:
1638 random.shuffle(tests)
1643 random.shuffle(tests)
1639 else:
1644 else:
1640 # keywords for slow tests
1645 # keywords for slow tests
1641 slow = 'svn gendoc check-code-hg'.split()
1646 slow = 'svn gendoc check-code-hg'.split()
1642 def sortkey(f):
1647 def sortkey(f):
1643 # run largest tests first, as they tend to take the longest
1648 # run largest tests first, as they tend to take the longest
1644 try:
1649 try:
1645 val = -os.stat(f).st_size
1650 val = -os.stat(f).st_size
1646 except OSError, e:
1651 except OSError as e:
1647 if e.errno != errno.ENOENT:
1652 if e.errno != errno.ENOENT:
1648 raise
1653 raise
1649 return -1e9 # file does not exist, tell early
1654 return -1e9 # file does not exist, tell early
1650 for kw in slow:
1655 for kw in slow:
1651 if kw in f:
1656 if kw in f:
1652 val *= 10
1657 val *= 10
1653 return val
1658 return val
1654 tests.sort(key=sortkey)
1659 tests.sort(key=sortkey)
1655
1660
1656 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1661 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1657
1662
1658 if 'PYTHONHASHSEED' not in os.environ:
1663 if 'PYTHONHASHSEED' not in os.environ:
1659 # use a random python hash seed all the time
1664 # use a random python hash seed all the time
1660 # we do the randomness ourself to know what seed is used
1665 # we do the randomness ourself to know what seed is used
1661 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1666 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1662
1667
1663 if self.options.tmpdir:
1668 if self.options.tmpdir:
1664 self.options.keep_tmpdir = True
1669 self.options.keep_tmpdir = True
1665 tmpdir = self.options.tmpdir
1670 tmpdir = self.options.tmpdir
1666 if os.path.exists(tmpdir):
1671 if os.path.exists(tmpdir):
1667 # Meaning of tmpdir has changed since 1.3: we used to create
1672 # Meaning of tmpdir has changed since 1.3: we used to create
1668 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1673 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1669 # tmpdir already exists.
1674 # tmpdir already exists.
1670 print "error: temp dir %r already exists" % tmpdir
1675 print("error: temp dir %r already exists" % tmpdir)
1671 return 1
1676 return 1
1672
1677
1673 # Automatically removing tmpdir sounds convenient, but could
1678 # Automatically removing tmpdir sounds convenient, but could
1674 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1679 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1675 # or "--tmpdir=$HOME".
1680 # or "--tmpdir=$HOME".
1676 #vlog("# Removing temp dir", tmpdir)
1681 #vlog("# Removing temp dir", tmpdir)
1677 #shutil.rmtree(tmpdir)
1682 #shutil.rmtree(tmpdir)
1678 os.makedirs(tmpdir)
1683 os.makedirs(tmpdir)
1679 else:
1684 else:
1680 d = None
1685 d = None
1681 if os.name == 'nt':
1686 if os.name == 'nt':
1682 # without this, we get the default temp dir location, but
1687 # without this, we get the default temp dir location, but
1683 # in all lowercase, which causes troubles with paths (issue3490)
1688 # in all lowercase, which causes troubles with paths (issue3490)
1684 d = os.getenv('TMP')
1689 d = os.getenv('TMP')
1685 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1690 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1686 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1691 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1687
1692
1688 if self.options.with_hg:
1693 if self.options.with_hg:
1689 self._installdir = None
1694 self._installdir = None
1690 self._bindir = os.path.dirname(os.path.realpath(
1695 self._bindir = os.path.dirname(os.path.realpath(
1691 self.options.with_hg))
1696 self.options.with_hg))
1692 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1697 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1693 os.makedirs(self._tmpbindir)
1698 os.makedirs(self._tmpbindir)
1694
1699
1695 # This looks redundant with how Python initializes sys.path from
1700 # This looks redundant with how Python initializes sys.path from
1696 # the location of the script being executed. Needed because the
1701 # the location of the script being executed. Needed because the
1697 # "hg" specified by --with-hg is not the only Python script
1702 # "hg" specified by --with-hg is not the only Python script
1698 # executed in the test suite that needs to import 'mercurial'
1703 # executed in the test suite that needs to import 'mercurial'
1699 # ... which means it's not really redundant at all.
1704 # ... which means it's not really redundant at all.
1700 self._pythondir = self._bindir
1705 self._pythondir = self._bindir
1701 else:
1706 else:
1702 self._installdir = os.path.join(self._hgtmp, "install")
1707 self._installdir = os.path.join(self._hgtmp, "install")
1703 self._bindir = os.environ["BINDIR"] = \
1708 self._bindir = os.environ["BINDIR"] = \
1704 os.path.join(self._installdir, "bin")
1709 os.path.join(self._installdir, "bin")
1705 self._tmpbindir = self._bindir
1710 self._tmpbindir = self._bindir
1706 self._pythondir = os.path.join(self._installdir, "lib", "python")
1711 self._pythondir = os.path.join(self._installdir, "lib", "python")
1707
1712
1708 os.environ["BINDIR"] = self._bindir
1713 os.environ["BINDIR"] = self._bindir
1709 os.environ["PYTHON"] = PYTHON
1714 os.environ["PYTHON"] = PYTHON
1710
1715
1711 runtestdir = os.path.abspath(os.path.dirname(__file__))
1716 runtestdir = os.path.abspath(os.path.dirname(__file__))
1712 path = [self._bindir, runtestdir] + os.environ["PATH"].split(os.pathsep)
1717 path = [self._bindir, runtestdir] + os.environ["PATH"].split(os.pathsep)
1713 if os.path.islink(__file__):
1718 if os.path.islink(__file__):
1714 # test helper will likely be at the end of the symlink
1719 # test helper will likely be at the end of the symlink
1715 realfile = os.path.realpath(__file__)
1720 realfile = os.path.realpath(__file__)
1716 realdir = os.path.abspath(os.path.dirname(realfile))
1721 realdir = os.path.abspath(os.path.dirname(realfile))
1717 path.insert(2, realdir)
1722 path.insert(2, realdir)
1718 if self._tmpbindir != self._bindir:
1723 if self._tmpbindir != self._bindir:
1719 path = [self._tmpbindir] + path
1724 path = [self._tmpbindir] + path
1720 os.environ["PATH"] = os.pathsep.join(path)
1725 os.environ["PATH"] = os.pathsep.join(path)
1721
1726
1722 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1727 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1723 # can run .../tests/run-tests.py test-foo where test-foo
1728 # can run .../tests/run-tests.py test-foo where test-foo
1724 # adds an extension to HGRC. Also include run-test.py directory to
1729 # adds an extension to HGRC. Also include run-test.py directory to
1725 # import modules like heredoctest.
1730 # import modules like heredoctest.
1726 pypath = [self._pythondir, self._testdir, runtestdir]
1731 pypath = [self._pythondir, self._testdir, runtestdir]
1727 # We have to augment PYTHONPATH, rather than simply replacing
1732 # We have to augment PYTHONPATH, rather than simply replacing
1728 # it, in case external libraries are only available via current
1733 # it, in case external libraries are only available via current
1729 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1734 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1730 # are in /opt/subversion.)
1735 # are in /opt/subversion.)
1731 oldpypath = os.environ.get(IMPL_PATH)
1736 oldpypath = os.environ.get(IMPL_PATH)
1732 if oldpypath:
1737 if oldpypath:
1733 pypath.append(oldpypath)
1738 pypath.append(oldpypath)
1734 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1739 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1735
1740
1736 if self.options.pure:
1741 if self.options.pure:
1737 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
1742 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
1738
1743
1739 self._coveragefile = os.path.join(self._testdir, '.coverage')
1744 self._coveragefile = os.path.join(self._testdir, '.coverage')
1740
1745
1741 vlog("# Using TESTDIR", self._testdir)
1746 vlog("# Using TESTDIR", self._testdir)
1742 vlog("# Using HGTMP", self._hgtmp)
1747 vlog("# Using HGTMP", self._hgtmp)
1743 vlog("# Using PATH", os.environ["PATH"])
1748 vlog("# Using PATH", os.environ["PATH"])
1744 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1749 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1745
1750
1746 try:
1751 try:
1747 return self._runtests(tests) or 0
1752 return self._runtests(tests) or 0
1748 finally:
1753 finally:
1749 time.sleep(.1)
1754 time.sleep(.1)
1750 self._cleanup()
1755 self._cleanup()
1751
1756
1752 def findtests(self, args):
1757 def findtests(self, args):
1753 """Finds possible test files from arguments.
1758 """Finds possible test files from arguments.
1754
1759
1755 If you wish to inject custom tests into the test harness, this would
1760 If you wish to inject custom tests into the test harness, this would
1756 be a good function to monkeypatch or override in a derived class.
1761 be a good function to monkeypatch or override in a derived class.
1757 """
1762 """
1758 if not args:
1763 if not args:
1759 if self.options.changed:
1764 if self.options.changed:
1760 proc = Popen4('hg st --rev "%s" -man0 .' %
1765 proc = Popen4('hg st --rev "%s" -man0 .' %
1761 self.options.changed, None, 0)
1766 self.options.changed, None, 0)
1762 stdout, stderr = proc.communicate()
1767 stdout, stderr = proc.communicate()
1763 args = stdout.strip('\0').split('\0')
1768 args = stdout.strip('\0').split('\0')
1764 else:
1769 else:
1765 args = os.listdir('.')
1770 args = os.listdir('.')
1766
1771
1767 return [t for t in args
1772 return [t for t in args
1768 if os.path.basename(t).startswith('test-')
1773 if os.path.basename(t).startswith('test-')
1769 and (t.endswith('.py') or t.endswith('.t'))]
1774 and (t.endswith('.py') or t.endswith('.t'))]
1770
1775
1771 def _runtests(self, tests):
1776 def _runtests(self, tests):
1772 try:
1777 try:
1773 if self._installdir:
1778 if self._installdir:
1774 self._installhg()
1779 self._installhg()
1775 self._checkhglib("Testing")
1780 self._checkhglib("Testing")
1776 else:
1781 else:
1777 self._usecorrectpython()
1782 self._usecorrectpython()
1778
1783
1779 if self.options.restart:
1784 if self.options.restart:
1780 orig = list(tests)
1785 orig = list(tests)
1781 while tests:
1786 while tests:
1782 if os.path.exists(tests[0] + ".err"):
1787 if os.path.exists(tests[0] + ".err"):
1783 break
1788 break
1784 tests.pop(0)
1789 tests.pop(0)
1785 if not tests:
1790 if not tests:
1786 print "running all tests"
1791 print("running all tests")
1787 tests = orig
1792 tests = orig
1788
1793
1789 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1794 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1790
1795
1791 failed = False
1796 failed = False
1792 warned = False
1797 warned = False
1793
1798
1794 suite = TestSuite(self._testdir,
1799 suite = TestSuite(self._testdir,
1795 jobs=self.options.jobs,
1800 jobs=self.options.jobs,
1796 whitelist=self.options.whitelisted,
1801 whitelist=self.options.whitelisted,
1797 blacklist=self.options.blacklist,
1802 blacklist=self.options.blacklist,
1798 retest=self.options.retest,
1803 retest=self.options.retest,
1799 keywords=self.options.keywords,
1804 keywords=self.options.keywords,
1800 loop=self.options.loop,
1805 loop=self.options.loop,
1801 runs_per_test=self.options.runs_per_test,
1806 runs_per_test=self.options.runs_per_test,
1802 tests=tests, loadtest=self._gettest)
1807 tests=tests, loadtest=self._gettest)
1803 verbosity = 1
1808 verbosity = 1
1804 if self.options.verbose:
1809 if self.options.verbose:
1805 verbosity = 2
1810 verbosity = 2
1806 runner = TextTestRunner(self, verbosity=verbosity)
1811 runner = TextTestRunner(self, verbosity=verbosity)
1807 result = runner.run(suite)
1812 result = runner.run(suite)
1808
1813
1809 if result.failures:
1814 if result.failures:
1810 failed = True
1815 failed = True
1811 if result.warned:
1816 if result.warned:
1812 warned = True
1817 warned = True
1813
1818
1814 if self.options.anycoverage:
1819 if self.options.anycoverage:
1815 self._outputcoverage()
1820 self._outputcoverage()
1816 except KeyboardInterrupt:
1821 except KeyboardInterrupt:
1817 failed = True
1822 failed = True
1818 print "\ninterrupted!"
1823 print("\ninterrupted!")
1819
1824
1820 if failed:
1825 if failed:
1821 return 1
1826 return 1
1822 if warned:
1827 if warned:
1823 return 80
1828 return 80
1824
1829
1825 def _getport(self, count):
1830 def _getport(self, count):
1826 port = self._ports.get(count) # do we have a cached entry?
1831 port = self._ports.get(count) # do we have a cached entry?
1827 if port is None:
1832 if port is None:
1828 port = self.options.port + self._portoffset
1833 port = self.options.port + self._portoffset
1829 portneeded = 3
1834 portneeded = 3
1830 # above 100 tries we just give up and let test reports failure
1835 # above 100 tries we just give up and let test reports failure
1831 for tries in xrange(100):
1836 for tries in xrange(100):
1832 allfree = True
1837 allfree = True
1833 for idx in xrange(portneeded):
1838 for idx in xrange(portneeded):
1834 if not checkportisavailable(port + idx):
1839 if not checkportisavailable(port + idx):
1835 allfree = False
1840 allfree = False
1836 break
1841 break
1837 self._portoffset += portneeded
1842 self._portoffset += portneeded
1838 if allfree:
1843 if allfree:
1839 break
1844 break
1840 self._ports[count] = port
1845 self._ports[count] = port
1841 return port
1846 return port
1842
1847
1843 def _gettest(self, test, count):
1848 def _gettest(self, test, count):
1844 """Obtain a Test by looking at its filename.
1849 """Obtain a Test by looking at its filename.
1845
1850
1846 Returns a Test instance. The Test may not be runnable if it doesn't
1851 Returns a Test instance. The Test may not be runnable if it doesn't
1847 map to a known type.
1852 map to a known type.
1848 """
1853 """
1849 lctest = test.lower()
1854 lctest = test.lower()
1850 testcls = Test
1855 testcls = Test
1851
1856
1852 for ext, cls in self.TESTTYPES:
1857 for ext, cls in self.TESTTYPES:
1853 if lctest.endswith(ext):
1858 if lctest.endswith(ext):
1854 testcls = cls
1859 testcls = cls
1855 break
1860 break
1856
1861
1857 refpath = os.path.join(self._testdir, test)
1862 refpath = os.path.join(self._testdir, test)
1858 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1863 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1859
1864
1860 t = testcls(refpath, tmpdir,
1865 t = testcls(refpath, tmpdir,
1861 keeptmpdir=self.options.keep_tmpdir,
1866 keeptmpdir=self.options.keep_tmpdir,
1862 debug=self.options.debug,
1867 debug=self.options.debug,
1863 timeout=self.options.timeout,
1868 timeout=self.options.timeout,
1864 startport=self._getport(count),
1869 startport=self._getport(count),
1865 extraconfigopts=self.options.extra_config_opt,
1870 extraconfigopts=self.options.extra_config_opt,
1866 py3kwarnings=self.options.py3k_warnings,
1871 py3kwarnings=self.options.py3k_warnings,
1867 shell=self.options.shell)
1872 shell=self.options.shell)
1868 t.should_reload = True
1873 t.should_reload = True
1869 return t
1874 return t
1870
1875
1871 def _cleanup(self):
1876 def _cleanup(self):
1872 """Clean up state from this test invocation."""
1877 """Clean up state from this test invocation."""
1873
1878
1874 if self.options.keep_tmpdir:
1879 if self.options.keep_tmpdir:
1875 return
1880 return
1876
1881
1877 vlog("# Cleaning up HGTMP", self._hgtmp)
1882 vlog("# Cleaning up HGTMP", self._hgtmp)
1878 shutil.rmtree(self._hgtmp, True)
1883 shutil.rmtree(self._hgtmp, True)
1879 for f in self._createdfiles:
1884 for f in self._createdfiles:
1880 try:
1885 try:
1881 os.remove(f)
1886 os.remove(f)
1882 except OSError:
1887 except OSError:
1883 pass
1888 pass
1884
1889
1885 def _usecorrectpython(self):
1890 def _usecorrectpython(self):
1886 """Configure the environment to use the appropriate Python in tests."""
1891 """Configure the environment to use the appropriate Python in tests."""
1887 # Tests must use the same interpreter as us or bad things will happen.
1892 # Tests must use the same interpreter as us or bad things will happen.
1888 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1893 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1889 if getattr(os, 'symlink', None):
1894 if getattr(os, 'symlink', None):
1890 vlog("# Making python executable in test path a symlink to '%s'" %
1895 vlog("# Making python executable in test path a symlink to '%s'" %
1891 sys.executable)
1896 sys.executable)
1892 mypython = os.path.join(self._tmpbindir, pyexename)
1897 mypython = os.path.join(self._tmpbindir, pyexename)
1893 try:
1898 try:
1894 if os.readlink(mypython) == sys.executable:
1899 if os.readlink(mypython) == sys.executable:
1895 return
1900 return
1896 os.unlink(mypython)
1901 os.unlink(mypython)
1897 except OSError, err:
1902 except OSError as err:
1898 if err.errno != errno.ENOENT:
1903 if err.errno != errno.ENOENT:
1899 raise
1904 raise
1900 if self._findprogram(pyexename) != sys.executable:
1905 if self._findprogram(pyexename) != sys.executable:
1901 try:
1906 try:
1902 os.symlink(sys.executable, mypython)
1907 os.symlink(sys.executable, mypython)
1903 self._createdfiles.append(mypython)
1908 self._createdfiles.append(mypython)
1904 except OSError, err:
1909 except OSError as err:
1905 # child processes may race, which is harmless
1910 # child processes may race, which is harmless
1906 if err.errno != errno.EEXIST:
1911 if err.errno != errno.EEXIST:
1907 raise
1912 raise
1908 else:
1913 else:
1909 exedir, exename = os.path.split(sys.executable)
1914 exedir, exename = os.path.split(sys.executable)
1910 vlog("# Modifying search path to find %s as %s in '%s'" %
1915 vlog("# Modifying search path to find %s as %s in '%s'" %
1911 (exename, pyexename, exedir))
1916 (exename, pyexename, exedir))
1912 path = os.environ['PATH'].split(os.pathsep)
1917 path = os.environ['PATH'].split(os.pathsep)
1913 while exedir in path:
1918 while exedir in path:
1914 path.remove(exedir)
1919 path.remove(exedir)
1915 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1920 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1916 if not self._findprogram(pyexename):
1921 if not self._findprogram(pyexename):
1917 print "WARNING: Cannot find %s in search path" % pyexename
1922 print("WARNING: Cannot find %s in search path" % pyexename)
1918
1923
1919 def _installhg(self):
1924 def _installhg(self):
1920 """Install hg into the test environment.
1925 """Install hg into the test environment.
1921
1926
1922 This will also configure hg with the appropriate testing settings.
1927 This will also configure hg with the appropriate testing settings.
1923 """
1928 """
1924 vlog("# Performing temporary installation of HG")
1929 vlog("# Performing temporary installation of HG")
1925 installerrs = os.path.join("tests", "install.err")
1930 installerrs = os.path.join("tests", "install.err")
1926 compiler = ''
1931 compiler = ''
1927 if self.options.compiler:
1932 if self.options.compiler:
1928 compiler = '--compiler ' + self.options.compiler
1933 compiler = '--compiler ' + self.options.compiler
1929 if self.options.pure:
1934 if self.options.pure:
1930 pure = "--pure"
1935 pure = "--pure"
1931 else:
1936 else:
1932 pure = ""
1937 pure = ""
1933 py3 = ''
1938 py3 = ''
1934 if sys.version_info[0] == 3:
1939 if sys.version_info[0] == 3:
1935 py3 = '--c2to3'
1940 py3 = '--c2to3'
1936
1941
1937 # Run installer in hg root
1942 # Run installer in hg root
1938 script = os.path.realpath(sys.argv[0])
1943 script = os.path.realpath(sys.argv[0])
1939 hgroot = os.path.dirname(os.path.dirname(script))
1944 hgroot = os.path.dirname(os.path.dirname(script))
1940 self._hgroot = hgroot
1945 self._hgroot = hgroot
1941 os.chdir(hgroot)
1946 os.chdir(hgroot)
1942 nohome = '--home=""'
1947 nohome = '--home=""'
1943 if os.name == 'nt':
1948 if os.name == 'nt':
1944 # The --home="" trick works only on OS where os.sep == '/'
1949 # The --home="" trick works only on OS where os.sep == '/'
1945 # because of a distutils convert_path() fast-path. Avoid it at
1950 # because of a distutils convert_path() fast-path. Avoid it at
1946 # least on Windows for now, deal with .pydistutils.cfg bugs
1951 # least on Windows for now, deal with .pydistutils.cfg bugs
1947 # when they happen.
1952 # when they happen.
1948 nohome = ''
1953 nohome = ''
1949 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1954 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1950 ' build %(compiler)s --build-base="%(base)s"'
1955 ' build %(compiler)s --build-base="%(base)s"'
1951 ' install --force --prefix="%(prefix)s"'
1956 ' install --force --prefix="%(prefix)s"'
1952 ' --install-lib="%(libdir)s"'
1957 ' --install-lib="%(libdir)s"'
1953 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1958 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1954 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1959 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1955 'compiler': compiler,
1960 'compiler': compiler,
1956 'base': os.path.join(self._hgtmp, "build"),
1961 'base': os.path.join(self._hgtmp, "build"),
1957 'prefix': self._installdir, 'libdir': self._pythondir,
1962 'prefix': self._installdir, 'libdir': self._pythondir,
1958 'bindir': self._bindir,
1963 'bindir': self._bindir,
1959 'nohome': nohome, 'logfile': installerrs})
1964 'nohome': nohome, 'logfile': installerrs})
1960
1965
1961 # setuptools requires install directories to exist.
1966 # setuptools requires install directories to exist.
1962 def makedirs(p):
1967 def makedirs(p):
1963 try:
1968 try:
1964 os.makedirs(p)
1969 os.makedirs(p)
1965 except OSError, e:
1970 except OSError as e:
1966 if e.errno != errno.EEXIST:
1971 if e.errno != errno.EEXIST:
1967 raise
1972 raise
1968 makedirs(self._pythondir)
1973 makedirs(self._pythondir)
1969 makedirs(self._bindir)
1974 makedirs(self._bindir)
1970
1975
1971 vlog("# Running", cmd)
1976 vlog("# Running", cmd)
1972 if os.system(cmd) == 0:
1977 if os.system(cmd) == 0:
1973 if not self.options.verbose:
1978 if not self.options.verbose:
1974 os.remove(installerrs)
1979 os.remove(installerrs)
1975 else:
1980 else:
1976 f = open(installerrs, 'rb')
1981 f = open(installerrs, 'rb')
1977 for line in f:
1982 for line in f:
1978 sys.stdout.write(line)
1983 sys.stdout.write(line)
1979 f.close()
1984 f.close()
1980 sys.exit(1)
1985 sys.exit(1)
1981 os.chdir(self._testdir)
1986 os.chdir(self._testdir)
1982
1987
1983 self._usecorrectpython()
1988 self._usecorrectpython()
1984
1989
1985 if self.options.py3k_warnings and not self.options.anycoverage:
1990 if self.options.py3k_warnings and not self.options.anycoverage:
1986 vlog("# Updating hg command to enable Py3k Warnings switch")
1991 vlog("# Updating hg command to enable Py3k Warnings switch")
1987 f = open(os.path.join(self._bindir, 'hg'), 'rb')
1992 f = open(os.path.join(self._bindir, 'hg'), 'rb')
1988 lines = [line.rstrip() for line in f]
1993 lines = [line.rstrip() for line in f]
1989 lines[0] += ' -3'
1994 lines[0] += ' -3'
1990 f.close()
1995 f.close()
1991 f = open(os.path.join(self._bindir, 'hg'), 'wb')
1996 f = open(os.path.join(self._bindir, 'hg'), 'wb')
1992 for line in lines:
1997 for line in lines:
1993 f.write(line + '\n')
1998 f.write(line + '\n')
1994 f.close()
1999 f.close()
1995
2000
1996 hgbat = os.path.join(self._bindir, 'hg.bat')
2001 hgbat = os.path.join(self._bindir, 'hg.bat')
1997 if os.path.isfile(hgbat):
2002 if os.path.isfile(hgbat):
1998 # hg.bat expects to be put in bin/scripts while run-tests.py
2003 # hg.bat expects to be put in bin/scripts while run-tests.py
1999 # installation layout put it in bin/ directly. Fix it
2004 # installation layout put it in bin/ directly. Fix it
2000 f = open(hgbat, 'rb')
2005 f = open(hgbat, 'rb')
2001 data = f.read()
2006 data = f.read()
2002 f.close()
2007 f.close()
2003 if '"%~dp0..\python" "%~dp0hg" %*' in data:
2008 if '"%~dp0..\python" "%~dp0hg" %*' in data:
2004 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
2009 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
2005 '"%~dp0python" "%~dp0hg" %*')
2010 '"%~dp0python" "%~dp0hg" %*')
2006 f = open(hgbat, 'wb')
2011 f = open(hgbat, 'wb')
2007 f.write(data)
2012 f.write(data)
2008 f.close()
2013 f.close()
2009 else:
2014 else:
2010 print 'WARNING: cannot fix hg.bat reference to python.exe'
2015 print('WARNING: cannot fix hg.bat reference to python.exe')
2011
2016
2012 if self.options.anycoverage:
2017 if self.options.anycoverage:
2013 custom = os.path.join(self._testdir, 'sitecustomize.py')
2018 custom = os.path.join(self._testdir, 'sitecustomize.py')
2014 target = os.path.join(self._pythondir, 'sitecustomize.py')
2019 target = os.path.join(self._pythondir, 'sitecustomize.py')
2015 vlog('# Installing coverage trigger to %s' % target)
2020 vlog('# Installing coverage trigger to %s' % target)
2016 shutil.copyfile(custom, target)
2021 shutil.copyfile(custom, target)
2017 rc = os.path.join(self._testdir, '.coveragerc')
2022 rc = os.path.join(self._testdir, '.coveragerc')
2018 vlog('# Installing coverage rc to %s' % rc)
2023 vlog('# Installing coverage rc to %s' % rc)
2019 os.environ['COVERAGE_PROCESS_START'] = rc
2024 os.environ['COVERAGE_PROCESS_START'] = rc
2020 covdir = os.path.join(self._installdir, '..', 'coverage')
2025 covdir = os.path.join(self._installdir, '..', 'coverage')
2021 try:
2026 try:
2022 os.mkdir(covdir)
2027 os.mkdir(covdir)
2023 except OSError, e:
2028 except OSError as e:
2024 if e.errno != errno.EEXIST:
2029 if e.errno != errno.EEXIST:
2025 raise
2030 raise
2026
2031
2027 os.environ['COVERAGE_DIR'] = covdir
2032 os.environ['COVERAGE_DIR'] = covdir
2028
2033
2029 def _checkhglib(self, verb):
2034 def _checkhglib(self, verb):
2030 """Ensure that the 'mercurial' package imported by python is
2035 """Ensure that the 'mercurial' package imported by python is
2031 the one we expect it to be. If not, print a warning to stderr."""
2036 the one we expect it to be. If not, print a warning to stderr."""
2032 if ((self._bindir == self._pythondir) and
2037 if ((self._bindir == self._pythondir) and
2033 (self._bindir != self._tmpbindir)):
2038 (self._bindir != self._tmpbindir)):
2034 # The pythondir has been inferred from --with-hg flag.
2039 # The pythondir has been inferred from --with-hg flag.
2035 # We cannot expect anything sensible here.
2040 # We cannot expect anything sensible here.
2036 return
2041 return
2037 expecthg = os.path.join(self._pythondir, 'mercurial')
2042 expecthg = os.path.join(self._pythondir, 'mercurial')
2038 actualhg = self._gethgpath()
2043 actualhg = self._gethgpath()
2039 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2044 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2040 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2045 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2041 ' (expected %s)\n'
2046 ' (expected %s)\n'
2042 % (verb, actualhg, expecthg))
2047 % (verb, actualhg, expecthg))
2043 def _gethgpath(self):
2048 def _gethgpath(self):
2044 """Return the path to the mercurial package that is actually found by
2049 """Return the path to the mercurial package that is actually found by
2045 the current Python interpreter."""
2050 the current Python interpreter."""
2046 if self._hgpath is not None:
2051 if self._hgpath is not None:
2047 return self._hgpath
2052 return self._hgpath
2048
2053
2049 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
2054 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
2050 pipe = os.popen(cmd % PYTHON)
2055 pipe = os.popen(cmd % PYTHON)
2051 try:
2056 try:
2052 self._hgpath = pipe.read().strip()
2057 self._hgpath = pipe.read().strip()
2053 finally:
2058 finally:
2054 pipe.close()
2059 pipe.close()
2055
2060
2056 return self._hgpath
2061 return self._hgpath
2057
2062
2058 def _outputcoverage(self):
2063 def _outputcoverage(self):
2059 """Produce code coverage output."""
2064 """Produce code coverage output."""
2060 from coverage import coverage
2065 from coverage import coverage
2061
2066
2062 vlog('# Producing coverage report')
2067 vlog('# Producing coverage report')
2063 # chdir is the easiest way to get short, relative paths in the
2068 # chdir is the easiest way to get short, relative paths in the
2064 # output.
2069 # output.
2065 os.chdir(self._hgroot)
2070 os.chdir(self._hgroot)
2066 covdir = os.path.join(self._installdir, '..', 'coverage')
2071 covdir = os.path.join(self._installdir, '..', 'coverage')
2067 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2072 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2068
2073
2069 # Map install directory paths back to source directory.
2074 # Map install directory paths back to source directory.
2070 cov.config.paths['srcdir'] = ['.', self._pythondir]
2075 cov.config.paths['srcdir'] = ['.', self._pythondir]
2071
2076
2072 cov.combine()
2077 cov.combine()
2073
2078
2074 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2079 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2075 cov.report(ignore_errors=True, omit=omit)
2080 cov.report(ignore_errors=True, omit=omit)
2076
2081
2077 if self.options.htmlcov:
2082 if self.options.htmlcov:
2078 htmldir = os.path.join(self._testdir, 'htmlcov')
2083 htmldir = os.path.join(self._testdir, 'htmlcov')
2079 cov.html_report(directory=htmldir, omit=omit)
2084 cov.html_report(directory=htmldir, omit=omit)
2080 if self.options.annotate:
2085 if self.options.annotate:
2081 adir = os.path.join(self._testdir, 'annotated')
2086 adir = os.path.join(self._testdir, 'annotated')
2082 if not os.path.isdir(adir):
2087 if not os.path.isdir(adir):
2083 os.mkdir(adir)
2088 os.mkdir(adir)
2084 cov.annotate(directory=adir, omit=omit)
2089 cov.annotate(directory=adir, omit=omit)
2085
2090
2086 def _findprogram(self, program):
2091 def _findprogram(self, program):
2087 """Search PATH for a executable program"""
2092 """Search PATH for a executable program"""
2088 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
2093 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
2089 name = os.path.join(p, program)
2094 name = os.path.join(p, program)
2090 if os.name == 'nt' or os.access(name, os.X_OK):
2095 if os.name == 'nt' or os.access(name, os.X_OK):
2091 return name
2096 return name
2092 return None
2097 return None
2093
2098
2094 def _checktools(self):
2099 def _checktools(self):
2095 """Ensure tools required to run tests are present."""
2100 """Ensure tools required to run tests are present."""
2096 for p in self.REQUIREDTOOLS:
2101 for p in self.REQUIREDTOOLS:
2097 if os.name == 'nt' and not p.endswith('.exe'):
2102 if os.name == 'nt' and not p.endswith('.exe'):
2098 p += '.exe'
2103 p += '.exe'
2099 found = self._findprogram(p)
2104 found = self._findprogram(p)
2100 if found:
2105 if found:
2101 vlog("# Found prerequisite", p, "at", found)
2106 vlog("# Found prerequisite", p, "at", found)
2102 else:
2107 else:
2103 print "WARNING: Did not find prerequisite tool: %s " % p
2108 print("WARNING: Did not find prerequisite tool: %s " % p)
2104
2109
2105 if __name__ == '__main__':
2110 if __name__ == '__main__':
2106 runner = TestRunner()
2111 runner = TestRunner()
2107
2112
2108 try:
2113 try:
2109 import msvcrt
2114 import msvcrt
2110 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2115 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2111 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2116 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2112 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2117 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2113 except ImportError:
2118 except ImportError:
2114 pass
2119 pass
2115
2120
2116 sys.exit(runner.run(sys.argv[1:]))
2121 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now