bitcoin/.github/ci-windows-cross.py
Ava Chow 92a3d30f38
Merge bitcoin/bitcoin#34418: qa: Make wallet_multiwallet.py Windows crossbuild-compatible
111864ac30126dc64a9e21d4e1b5e3d9ef4e5358 qa: Avoid duplicating output in case the diff is the same (Hodlinator)
c2e28d455af8fbb8d6074dc26590e61b9764d761 ci: Enable `wallet_multiwallet.py` in "Windows, test cross-built" job (Hodlinator)
850a80c1999e671b6cce33d8545af06adf5f77f0 qa: Disable parts of the test when running under Windows or root (Hodlinator)
fb803e3c79e52305df74ae30e77fd36900a49c24 qa: Test scanning errors individually (Hodlinator)
ed43ce57cce53612f13ac7c6db59fa7ac60e31c4 qa: Check for platform-independent part of error message (Hodlinator)
64a098a9b6263dbdeea25f89f4c9fe3c53943dd1 refactor(qa): Break apart ginormous run_test() (Hodlinator)
bb1aff7ed7e4bd6618dfe75b5faa9956c3adead4 move-only(qa): Move wallet creation check down to others (Hodlinator)
d1a4ddb58ef676d4e7436cc3bcdf5fb3008b4b6f refactor(qa): Lift out functions to outer scopes (Hodlinator)
c811e47367d531b69c10e3fc976df764e79f13e2 scripted-diff: self.nodes[0] => node (Hodlinator)
73cf858911056717a4ebe97cd250f3a506136eff refactor(qa): Remove unused option (Hodlinator)

Pull request description:

  Makes the functional test compatible with *Linux->Windows cross-built executables*.

  Main parts:
  * Commit "qa: Check for platform-independent part of error message" switches to match on platform-independent part of error message.
  * Commit "qa: Test scanning errors individually" disentangles code causing the same error message substring, based on #31410.
  * Commit "qa: Disable parts of the test when running under Windows or root" enables the test to be run on Windows, based in part on https://github.com/bitcoin/bitcoin/pull/31410#issuecomment-3554721014.

  Also:
  * Removes unused option in wallet_multiwallet.py.
  * Breaks apart wallet_multiwallet.py's `run_test()` into smaller test functions.
  * Improves `assert_equal()` output for dicts.

  Fixes #31409.

ACKs for top commit:
  achow101:
    ACK 111864ac30126dc64a9e21d4e1b5e3d9ef4e5358
  janb84:
    re ACK 111864ac30126dc64a9e21d4e1b5e3d9ef4e5358
  w0xlt:
    reACK 111864ac30126dc64a9e21d4e1b5e3d9ef4e5358

Tree-SHA512: 4e3ff92588ac9f2611fc963ce62097b6c0dd4d4eb8da7952c72619c7b554ff3cae5163fe1886d4d9bbd7af1acca5b846411e7f5b46f9bddb08719b61108efbba
2026-03-13 15:45:47 -07:00

161 lines
4.9 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.
import argparse
import os
import shlex
import subprocess
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).resolve().parent.parent / "test"))
from download_utils import download_script_assets
def run(cmd, **kwargs):
print("+ " + shlex.join(cmd), flush=True)
kwargs.setdefault("check", True)
try:
return subprocess.run(cmd, **kwargs)
except Exception as e:
sys.exit(str(e))
def print_version():
bitcoind = Path.cwd() / "bin" / "bitcoind.exe"
run([str(bitcoind), "-version"])
def check_manifests():
release_dir = Path.cwd() / "bin"
manifest_path = release_dir / "bitcoind.manifest"
cmd_bitcoind_manifest = [
"mt.exe",
"-nologo",
f"-inputresource:{release_dir / 'bitcoind.exe'}",
f"-out:{manifest_path}",
]
run(cmd_bitcoind_manifest)
print(manifest_path.read_text())
skipped = { # Skip as they currently do not have manifests
"fuzz.exe",
"bench_bitcoin.exe",
}
for entry in release_dir.iterdir():
if entry.suffix.lower() != ".exe":
continue
if entry.name in skipped:
print(f"Skipping {entry.name} (no manifest present)")
continue
print(f"Checking {entry.name}")
run(["mt.exe", "-nologo", f"-inputresource:{entry}", "-validate_manifest"])
def prepare_tests():
workspace = Path.cwd()
config_path = workspace / "test" / "config.ini"
rpcauth_path = workspace / "share" / "rpcauth" / "rpcauth.py"
replacements = {
"SRCDIR=": f"SRCDIR={workspace}",
"BUILDDIR=": f"BUILDDIR={workspace}",
"RPCAUTH=": f"RPCAUTH={rpcauth_path}",
}
lines = config_path.read_text().splitlines()
for index, line in enumerate(lines):
for prefix, new_value in replacements.items():
if line.startswith(prefix):
lines[index] = new_value
break
content = "\n".join(lines) + "\n"
config_path.write_text(content)
print(content)
previous_releases_dir = Path(os.environ["PREVIOUS_RELEASES_DIR"])
cmd_download_prev_rel = [
sys.executable,
str(workspace / "test" / "get_previous_releases.py"),
"--target-dir",
str(previous_releases_dir),
]
run(cmd_download_prev_rel)
run([sys.executable, "-m", "pip", "install", "pyzmq"])
dest = workspace / "unit_test_data"
download_script_assets(dest)
def run_functional_tests():
workspace = Path.cwd()
num_procs = str(os.process_cpu_count())
test_runner_cmd = [
sys.executable,
str(workspace / "test" / "functional" / "test_runner.py"),
"--jobs",
num_procs,
"--quiet",
f"--tmpdirprefix={workspace}",
"--combinedlogslen=99999999",
*shlex.split(os.environ.get("TEST_RUNNER_EXTRA", "").strip()),
# feature_unsupported_utxo_db.py fails on Windows because of emojis in the test data directory.
"--exclude",
"feature_unsupported_utxo_db.py",
]
run(test_runner_cmd)
# Run feature_unsupported_utxo_db sequentially in ASCII-only tmp dir,
# because it is excluded above due to lack of UTF-8 support in the
# ancient release.
cmd_feature_unsupported_db = [
sys.executable,
str(workspace / "test" / "functional" / "feature_unsupported_utxo_db.py"),
"--previous-releases",
"--tmpdir",
str(Path(workspace) / "test_feature_unsupported_utxo_db"),
]
run(cmd_feature_unsupported_db)
def run_unit_tests():
workspace = Path.cwd()
os.environ["DIR_UNIT_TEST_DATA"] = str(workspace / "unit_test_data")
# Can't use ctest here like other jobs as we don't have a CMake build tree.
commands = [
["./bin/test_bitcoin-qt.exe"],
# Intentionally run sequentially here, to catch test case failures caused by dirty global state from prior test cases:
["./bin/test_bitcoin.exe", "-l", "test_suite"],
["./src/secp256k1/bin/exhaustive_tests.exe"],
["./src/secp256k1/bin/noverify_tests.exe"],
["./src/secp256k1/bin/tests.exe"],
["./src/univalue/object.exe"],
["./src/univalue/unitester.exe"],
]
for cmd in commands:
run(cmd)
def main():
parser = argparse.ArgumentParser(description="Utility to run Windows CI steps.")
steps = list(map(lambda f: f.__name__, [
print_version,
check_manifests,
prepare_tests,
run_unit_tests,
run_functional_tests,
]))
parser.add_argument("step", choices=steps, help="CI step to perform.")
args = parser.parse_args()
os.environ.setdefault(
"PREVIOUS_RELEASES_DIR",
str(Path.cwd() / "previous_releases"),
)
exec(f'{args.step}()')
if __name__ == "__main__":
main()