Skip to content

Commit 990fc0d

Browse files
author
MarcoFalke
committed
Merge #14007: tests: Run functional test on Windows and enable it on Appveyor
661ac15 appveyor: Run functional tests on appveyor (Chun Kuan Lee) 2148c36 tests: Make it possible to run functional tests on Windows (Chun Kuan Lee) Pull request description: This PR do the following things: - Make functional tests compatible with Windows - Print color output in functional tests for Windows 10 - Run util and functional tests on appveyor - Do not run symlink tests on Windows Note: - The wallet_multiwallet.py fail is unrelated to the test framework, it's a bug related to c++ code or maybe dependencies. `bitcoind` would exit with 0xC0000005(Access violation) during shutdown occasionally. Disable this for now. - Not using `--failfast` because this is still in experimental. We should track if there is any other error. - Disable ZMQ tests because the python zmq library could cause access violation sometimes. - Disable `feature_notifications` because Bitcoin Core handles the command in different thread, whicha can cause a race condition. Tree-SHA512: b76db137d264e62a5c130e1cbca7a2ca002a7a0f4153fa0b92c1ea6c9c09ef0533e11c49bdbd566c472d8ff59f245758feb5e5a6ec6cb6bb66a1c67bab5fa48a
2 parents 3761209 + 661ac15 commit 990fc0d

File tree

6 files changed

+74
-27
lines changed

6 files changed

+74
-27
lines changed

appveyor.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ environment:
77
APPVEYOR_SAVE_CACHE_ON_ERROR: true
88
CLCACHE_SERVER: 1
99
PACKAGES: boost-filesystem boost-signals2 boost-interprocess boost-test libevent openssl zeromq berkeleydb secp256k1 leveldb
10+
PYTHONIOENCODING: utf-8
1011
cache:
1112
- C:\tools\vcpkg\installed
1213
- C:\Users\appveyor\clcache
@@ -15,6 +16,8 @@ init:
1516
- cmd: set PATH=C:\Python36-x64;C:\Python36-x64\Scripts;%PATH%
1617
install:
1718
- cmd: pip install git+https://github.com/frerich/clcache.git
19+
# Disable zmq test for now since python zmq library on Windows would cause Access violation sometimes.
20+
# - cmd: pip install zmq
1821
- ps: $packages = $env:PACKAGES -Split ' '
1922
- ps: for ($i=0; $i -lt $packages.length; $i++) {
2023
$env:ALL_PACKAGES += $packages[$i] + ":" + $env:PLATFORM + "-windows-static "
@@ -40,6 +43,17 @@ after_build:
4043
- cmd: move build_msvc\%PLATFORM%\%CONFIGURATION%\*.iobj build_msvc\cache\
4144
- cmd: move build_msvc\%PLATFORM%\%CONFIGURATION%\*.ipdb build_msvc\cache\
4245
- cmd: del C:\Users\appveyor\clcache\stats.txt
46+
before_test:
47+
- ps: ${conf_ini} = (Get-Content([IO.Path]::Combine(${env:APPVEYOR_BUILD_FOLDER}, "test", "config.ini.in")))
48+
- ps: ${conf_ini} = $conf_ini.Replace("@abs_top_srcdir@", ${env:APPVEYOR_BUILD_FOLDER}).Replace("@abs_top_builddir@", ${env:APPVEYOR_BUILD_FOLDER}).Replace("@EXEEXT@", ".exe")
49+
- ps: ${conf_ini} = $conf_ini.Replace("@ENABLE_WALLET_TRUE@", "").Replace("@BUILD_BITCOIN_UTILS_TRUE@", "").Replace("@BUILD_BITCOIND_TRUE@", "").Replace("@ENABLE_ZMQ_TRUE@", "")
50+
- ps: ${utf8} = New-Object System.Text.UTF8Encoding ${false}
51+
- ps: '[IO.File]::WriteAllLines([IO.Path]::Combine(${env:APPVEYOR_BUILD_FOLDER}, "test", "config.ini"), $conf_ini, ${utf8})'
52+
- ps: move "build_msvc\${env:PLATFORM}\${env:CONFIGURATION}\*.exe" src
4353
test_script:
44-
- cmd: build_msvc\%PLATFORM%\%CONFIGURATION%\test_bitcoin.exe
54+
- cmd: src\test_bitcoin.exe
55+
- ps: src\bench_bitcoin.exe -evals=1 -scaling=0
56+
- ps: python test\util\bitcoin-util-test.py
57+
- cmd: python test\util\rpcauth-test.py
58+
- cmd: python test\functional\test_runner.py --force --quiet --combinedlogslen=4000 --exclude "feature_notifications,wallet_multiwallet,wallet_multiwallet.py --usecli"
4559
deploy: off

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
@@ -824,7 +824,7 @@ def run_test(self):
824824
tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0)))
825825
b64a = self.update_block("64a", [tx])
826826
assert_equal(len(b64a.serialize()), MAX_BLOCK_BASE_SIZE + 8)
827-
self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize(): iostream error')
827+
self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize():')
828828

829829
# bitcoind doesn't disconnect us for sending a bloated block, but if we subsequently
830830
# 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)