/ .github / ci-windows-cross.py
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()