ci-windows-cross.py
1 #!/usr/bin/env python3 2 # Copyright (c) The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or https://opensource.org/license/mit/. 5 6 import argparse 7 import os 8 import shlex 9 import subprocess 10 import sys 11 from pathlib import Path 12 13 sys.path.append(str(Path(__file__).resolve().parent.parent / "test")) 14 from download_utils import download_script_assets 15 16 17 def run(cmd, **kwargs): 18 print("+ " + shlex.join(cmd), flush=True) 19 kwargs.setdefault("check", True) 20 try: 21 return subprocess.run(cmd, **kwargs) 22 except Exception as e: 23 sys.exit(str(e)) 24 25 26 def print_version(): 27 bitcoind = Path.cwd() / "bin" / "bitcoind.exe" 28 run([str(bitcoind), "-version"]) 29 30 31 def check_manifests(): 32 release_dir = Path.cwd() / "bin" 33 manifest_path = release_dir / "bitcoind.manifest" 34 35 cmd_bitcoind_manifest = [ 36 "mt.exe", 37 "-nologo", 38 f"-inputresource:{release_dir / 'bitcoind.exe'}", 39 f"-out:{manifest_path}", 40 ] 41 run(cmd_bitcoind_manifest) 42 print(manifest_path.read_text()) 43 44 skipped = { # Skip as they currently do not have manifests 45 "fuzz.exe", 46 "bench_bitcoin.exe", 47 } 48 for entry in release_dir.iterdir(): 49 if entry.suffix.lower() != ".exe": 50 continue 51 if entry.name in skipped: 52 print(f"Skipping {entry.name} (no manifest present)") 53 continue 54 print(f"Checking {entry.name}") 55 run(["mt.exe", "-nologo", f"-inputresource:{entry}", "-validate_manifest"]) 56 57 58 def prepare_tests(): 59 workspace = Path.cwd() 60 config_path = workspace / "test" / "config.ini" 61 rpcauth_path = workspace / "share" / "rpcauth" / "rpcauth.py" 62 replacements = { 63 "SRCDIR=": f"SRCDIR={workspace}", 64 "BUILDDIR=": f"BUILDDIR={workspace}", 65 "RPCAUTH=": f"RPCAUTH={rpcauth_path}", 66 } 67 lines = config_path.read_text().splitlines() 68 for index, line in enumerate(lines): 69 for prefix, new_value in replacements.items(): 70 if line.startswith(prefix): 71 lines[index] = new_value 72 break 73 content = "\n".join(lines) + "\n" 74 config_path.write_text(content) 75 print(content) 76 previous_releases_dir = Path(os.environ["PREVIOUS_RELEASES_DIR"]) 77 cmd_download_prev_rel = [ 78 sys.executable, 79 str(workspace / "test" / "get_previous_releases.py"), 80 "--target-dir", 81 str(previous_releases_dir), 82 ] 83 run(cmd_download_prev_rel) 84 run([sys.executable, "-m", "pip", "install", "pyzmq"]) 85 86 dest = workspace / "unit_test_data" 87 download_script_assets(dest) 88 89 90 def run_functional_tests(): 91 workspace = Path.cwd() 92 num_procs = str(os.process_cpu_count()) 93 test_runner_cmd = [ 94 sys.executable, 95 str(workspace / "test" / "functional" / "test_runner.py"), 96 "--jobs", 97 num_procs, 98 "--quiet", 99 f"--tmpdirprefix={workspace}", 100 "--combinedlogslen=99999999", 101 *shlex.split(os.environ.get("TEST_RUNNER_EXTRA", "").strip()), 102 # feature_unsupported_utxo_db.py fails on Windows because of emojis in the test data directory. 103 "--exclude", 104 "feature_unsupported_utxo_db.py", 105 ] 106 run(test_runner_cmd) 107 108 # Run feature_unsupported_utxo_db sequentially in ASCII-only tmp dir, 109 # because it is excluded above due to lack of UTF-8 support in the 110 # ancient release. 111 cmd_feature_unsupported_db = [ 112 sys.executable, 113 str(workspace / "test" / "functional" / "feature_unsupported_utxo_db.py"), 114 "--previous-releases", 115 "--tmpdir", 116 str(Path(workspace) / "test_feature_unsupported_utxo_db"), 117 ] 118 run(cmd_feature_unsupported_db) 119 120 121 def run_unit_tests(): 122 workspace = Path.cwd() 123 os.environ["DIR_UNIT_TEST_DATA"] = str(workspace / "unit_test_data") 124 # Can't use ctest here like other jobs as we don't have a CMake build tree. 125 commands = [ 126 ["./bin/test_bitcoin-qt.exe"], 127 # Intentionally run sequentially here, to catch test case failures caused by dirty global state from prior test cases: 128 ["./bin/test_bitcoin.exe", "-l", "test_suite"], 129 ["./src/secp256k1/bin/exhaustive_tests.exe"], 130 ["./src/secp256k1/bin/noverify_tests.exe"], 131 ["./src/secp256k1/bin/tests.exe"], 132 ["./src/univalue/object.exe"], 133 ["./src/univalue/unitester.exe"], 134 ] 135 for cmd in commands: 136 run(cmd) 137 138 139 def main(): 140 parser = argparse.ArgumentParser(description="Utility to run Windows CI steps.") 141 steps = list(map(lambda f: f.__name__, [ 142 print_version, 143 check_manifests, 144 prepare_tests, 145 run_unit_tests, 146 run_functional_tests, 147 ])) 148 parser.add_argument("step", choices=steps, help="CI step to perform.") 149 args = parser.parse_args() 150 151 os.environ.setdefault( 152 "PREVIOUS_RELEASES_DIR", 153 str(Path.cwd() / "previous_releases"), 154 ) 155 156 exec(f'{args.step}()') 157 158 159 if __name__ == "__main__": 160 main()