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