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