ci-windows-cross.py raw
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()
161