Skip to content

Commit 2148c36

Browse files
committed
tests: Make it possible to run functional tests on Windows
1 parent 3832c25 commit 2148c36

File tree

5 files changed

+59
-26
lines changed

5 files changed

+59
-26
lines changed

test/functional/combine_logs.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ def main():
2525
parser.add_argument('--html', dest='html', action='store_true', help='outputs the combined log as html. Requires jinja2. pip install jinja2')
2626
args, unknown_args = parser.parse_known_args()
2727

28-
if args.color and os.name != 'posix':
29-
print("Color output requires posix terminal colors.")
30-
sys.exit(1)
31-
3228
if args.html and args.color:
3329
print("Only one out of --color or --html should be specified")
3430
sys.exit(1)

test/functional/feature_block.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ def run_test(self):
827827
tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0)))
828828
b64a = self.update_block("64a", [tx])
829829
assert_equal(len(b64a.serialize()), MAX_BLOCK_BASE_SIZE + 8)
830-
self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize(): iostream error')
830+
self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize():')
831831

832832
# bitcoind doesn't disconnect us for sending a bloated block, but if we subsequently
833833
# resend the header message, it won't send us the getdata message again. Just

test/functional/test_framework/authproxy.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import http.client
3939
import json
4040
import logging
41+
import os
4142
import socket
4243
import time
4344
import urllib.parse
@@ -71,19 +72,12 @@ def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connect
7172
self._service_name = service_name
7273
self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests
7374
self.__url = urllib.parse.urlparse(service_url)
74-
port = 80 if self.__url.port is None else self.__url.port
7575
user = None if self.__url.username is None else self.__url.username.encode('utf8')
7676
passwd = None if self.__url.password is None else self.__url.password.encode('utf8')
7777
authpair = user + b':' + passwd
7878
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
79-
80-
if connection:
81-
# Callables re-use the connection of the original proxy
82-
self.__conn = connection
83-
elif self.__url.scheme == 'https':
84-
self.__conn = http.client.HTTPSConnection(self.__url.hostname, port, timeout=timeout)
85-
else:
86-
self.__conn = http.client.HTTPConnection(self.__url.hostname, port, timeout=timeout)
79+
self.timeout = timeout
80+
self._set_conn(connection)
8781

8882
def __getattr__(self, name):
8983
if name.startswith('__') and name.endswith('__'):
@@ -102,6 +96,10 @@ def _request(self, method, path, postdata):
10296
'User-Agent': USER_AGENT,
10397
'Authorization': self.__auth_header,
10498
'Content-type': 'application/json'}
99+
if os.name == 'nt':
100+
# Windows somehow does not like to re-use connections
101+
# TODO: Find out why the connection would disconnect occasionally and make it reusable on Windows
102+
self._set_conn()
105103
try:
106104
self.__conn.request(method, path, postdata, headers)
107105
return self._get_response()
@@ -178,3 +176,13 @@ def _get_response(self):
178176

179177
def __truediv__(self, relative_uri):
180178
return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)
179+
180+
def _set_conn(self, connection=None):
181+
port = 80 if self.__url.port is None else self.__url.port
182+
if connection:
183+
self.__conn = connection
184+
self.timeout = connection.timeout
185+
elif self.__url.scheme == 'https':
186+
self.__conn = http.client.HTTPSConnection(self.__url.hostname, port, timeout=self.timeout)
187+
else:
188+
self.__conn = http.client.HTTPConnection(self.__url.hostname, port, timeout=self.timeout)

test/functional/test_runner.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import logging
3030

3131
# Formatting. Default colors to empty strings.
32-
BOLD, BLUE, RED, GREY = ("", ""), ("", ""), ("", ""), ("", "")
32+
BOLD, GREEN, RED, GREY = ("", ""), ("", ""), ("", ""), ("", "")
3333
try:
3434
# Make sure python thinks it can write unicode to its stdout
3535
"\u2713".encode("utf_8").decode(sys.stdout.encoding)
@@ -41,11 +41,27 @@
4141
CROSS = "x "
4242
CIRCLE = "o "
4343

44-
if os.name == 'posix':
44+
if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393):
45+
if os.name == 'nt':
46+
import ctypes
47+
kernel32 = ctypes.windll.kernel32
48+
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
49+
STD_OUTPUT_HANDLE = -11
50+
STD_ERROR_HANDLE = -12
51+
# Enable ascii color control to stdout
52+
stdout = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
53+
stdout_mode = ctypes.c_int32()
54+
kernel32.GetConsoleMode(stdout, ctypes.byref(stdout_mode))
55+
kernel32.SetConsoleMode(stdout, stdout_mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
56+
# Enable ascii color control to stderr
57+
stderr = kernel32.GetStdHandle(STD_ERROR_HANDLE)
58+
stderr_mode = ctypes.c_int32()
59+
kernel32.GetConsoleMode(stderr, ctypes.byref(stderr_mode))
60+
kernel32.SetConsoleMode(stderr, stderr_mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
4561
# primitive formatting on supported
4662
# terminal via ANSI escape sequences:
4763
BOLD = ('\033[0m', '\033[1m')
48-
BLUE = ('\033[0m', '\033[0;34m')
64+
GREEN = ('\033[0m', '\033[0;32m')
4965
RED = ('\033[0m', '\033[0;31m')
5066
GREY = ('\033[0m', '\033[1;30m')
5167

@@ -227,6 +243,11 @@ def main():
227243

228244
# Create base test directory
229245
tmpdir = "%s/test_runner_₿_🏃_%s" % (args.tmpdirprefix, datetime.datetime.now().strftime("%Y%m%d_%H%M%S"))
246+
247+
# If we fixed the command-line and filename encoding issue on Windows, these two lines could be removed
248+
if config["environment"]["EXEEXT"] == ".exe":
249+
tmpdir = "%s/test_runner_%s" % (args.tmpdirprefix, datetime.datetime.now().strftime("%Y%m%d_%H%M%S"))
250+
230251
os.makedirs(tmpdir)
231252

232253
logging.debug("Temporary test directory at %s" % tmpdir)
@@ -264,7 +285,7 @@ def main():
264285

265286
# Remove the test cases that the user has explicitly asked to exclude.
266287
if args.exclude:
267-
exclude_tests = [re.sub("\.py$", "", test) + ".py" for test in args.exclude.split(',')]
288+
exclude_tests = [re.sub("\.py$", "", test) + (".py" if ".py" not in test else "") for test in args.exclude.split(',')]
268289
for exclude_test in exclude_tests:
269290
if exclude_test in test_list:
270291
test_list.remove(exclude_test)
@@ -359,7 +380,10 @@ def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=Fal
359380
print('\n============')
360381
print('{}Combined log for {}:{}'.format(BOLD[1], testdir, BOLD[0]))
361382
print('============\n')
362-
combined_logs, _ = subprocess.Popen([sys.executable, os.path.join(tests_dir, 'combine_logs.py'), '-c', testdir], universal_newlines=True, stdout=subprocess.PIPE).communicate()
383+
combined_logs_args = [sys.executable, os.path.join(tests_dir, 'combine_logs.py'), testdir]
384+
if BOLD[0]:
385+
combined_logs_args += ['--color']
386+
combined_logs, _ = subprocess.Popen(combined_logs_args, universal_newlines=True, stdout=subprocess.PIPE).communicate()
363387
print("\n".join(deque(combined_logs.splitlines(), combined_logs_len)))
364388

365389
if failfast:
@@ -498,7 +522,7 @@ def sort_key(self):
498522

499523
def __repr__(self):
500524
if self.status == "Passed":
501-
color = BLUE
525+
color = GREEN
502526
glyph = TICK
503527
elif self.status == "Failed":
504528
color = RED

test/functional/wallet_multiwallet.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ def wallet_file(name):
4444

4545
# create symlink to verify wallet directory path can be referenced
4646
# through symlink
47-
os.mkdir(wallet_dir('w7'))
48-
os.symlink('w7', wallet_dir('w7_symlink'))
47+
if os.name != 'nt':
48+
os.mkdir(wallet_dir('w7'))
49+
os.symlink('w7', wallet_dir('w7_symlink'))
4950

5051
# rename wallet.dat to make sure plain wallet file paths (as opposed to
5152
# directory paths) can be loaded
@@ -66,6 +67,8 @@ def wallet_file(name):
6667
# w8 - to verify existing wallet file is loaded correctly
6768
# '' - to verify default wallet file is created correctly
6869
wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', '']
70+
if os.name == 'nt':
71+
wallet_names.remove('w7_symlink')
6972
extra_args = ['-wallet={}'.format(n) for n in wallet_names]
7073
self.start_node(0, extra_args)
7174
assert_equal(set(node.listwallets()), set(wallet_names))
@@ -76,7 +79,7 @@ def wallet_file(name):
7679
assert_equal(os.path.isfile(wallet_file(wallet_name)), True)
7780

7881
# should not initialize if wallet path can't be created
79-
exp_stderr = "boost::filesystem::create_directory: (The system cannot find the path specified|Not a directory):"
82+
exp_stderr = "boost::filesystem::create_directory:"
8083
self.nodes[0].assert_start_raises_init_error(['-wallet=wallet.dat/bad'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
8184

8285
self.nodes[0].assert_start_raises_init_error(['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist')
@@ -92,8 +95,9 @@ def wallet_file(name):
9295
self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
9396

9497
# should not initialize if wallet file is a symlink
95-
os.symlink('w8', wallet_dir('w8_symlink'))
96-
self.nodes[0].assert_start_raises_init_error(['-wallet=w8_symlink'], 'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX)
98+
if os.name != 'nt':
99+
os.symlink('w8', wallet_dir('w8_symlink'))
100+
self.nodes[0].assert_start_raises_init_error(['-wallet=w8_symlink'], 'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX)
97101

98102
# should not initialize if the specified walletdir does not exist
99103
self.nodes[0].assert_start_raises_init_error(['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist')
@@ -220,7 +224,8 @@ def wallet_file(name):
220224
assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
221225

222226
# Fail to load if wallet file is a symlink
223-
assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink')
227+
if os.name != 'nt':
228+
assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink')
224229

225230
# Fail to load if a directory is specified that doesn't contain a wallet
226231
os.mkdir(wallet_dir('empty_wallet_dir'))

0 commit comments

Comments
 (0)