Repository: bitcoin/bitcoin
File: test/get_previous_releases.py (Python)

Code before:
        bin_path = 'bin/bitcoin-core-{}/test.{}'.format(
            match.group(1), match.group(2))
    platform = args.platform
    if tag < "v23" and platform in ["x86_64-apple-darwin", "arm64-apple-darwin"]:
        platform = "osx64"
    tarball = 'bitcoin-{tag}-{platform}.tar.gz'.format(
        tag=tag[1:], platform=platform)
    tarballUrl = 'https://bitcoincore.org/{bin_path}/{tarball}'.format(
        bin_path=bin_path, tarball=tarball)

    print('Fetching: {tarballUrl}'.format(tarballUrl=tarballUrl))

    ret = subprocess.run(['curl', '--fail', '--remote-name', tarballUrl]).returncode
    if ret:
        return ret

    hasher = hashlib.sha256()
    with open(tarball, "rb") as afile:
        hasher.update(afile.read())
    tarballHash = hasher.hexdigest()

    if tarballHash not in SHA256_SUMS or SHA256_SUMS[tarballHash]['tarball'] != tarball:
        if tarball in [v['tarball'] for v in SHA256_SUMS.values()]:
            print("Checksum did not match")
            return 1

        print("Checksum for given version doesn't exist")
        return 1
    print("Checksum matched")

    # Extract tarball
    ret = subprocess.run(['tar', '-zxf', tarball, '-C', tag,
                          '--strip-components=1',
                          'bitcoin-{tag}'.format(tag=tag[1:])]).returncode
    if ret != 0:
        print(f"Failed to extract the {tag} tarball")
        return ret

    Path(tarball).unlink()

    if tag >= "v23" and platform == "arm64-apple-darwin":
        # Starting with v23 there are arm64 binaries for ARM (e.g. M1, M2) macs, but they have to be signed to run
        binary_path = f'{os.getcwd()}/{tag}/bin/'

        for arm_binary in os.listdir(binary_path):
            # Is it already signed?
            ret = subprocess.run(
                ['codesign', '-v', binary_path + arm_binary],
                stderr=subprocess.DEVNULL,  # Suppress expected stderr output
            ).returncode
            if ret == 1:

Review comment (bug):
nit: Could postpone creating the version-directory until we've verified the checksum and are ready to extract. This avoids the block above ("Using cached ...") thinking it has a cached copy in case the code has failed further down and the directory is just empty. (Also slight improvement to error handling).

```diff
diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py
index 955fcf15d1..7e5c66de0f 100755
--- a/test/get_previous_releases.py
+++ b/test/get_previous_releases.py
@@ -127,7 +150,6 @@ def download_binary(tag, args) -> int:
             print('Using cached {}'.format(tag))
             return 0
         shutil.rmtree(tag)
-    Path(tag).mkdir()

     bin_path = 'bin/bitcoin-core-{}'.format(tag[1:])

@@ -162,13 +184,13 @@ def download_binary(tag, args) -> int:

     if archiveHash not in SHA256_SUMS or SHA256_SUMS[archiveHash]['archive'] != archive:
         if archive in [v['archive'] for v in SHA256_SUMS.values()]:
-            print("Checksum did not match", file=sys.stderr)
-            return 1
-
-        print("Checksum for given version doesn't exist", file=sys.stderr)
+            print(f"Checksum {archiveHash} did not match", file=sys.stderr)
+        else:
+            print("Checksum for given version doesn't exist", file=sys.stderr)
         return 1
     print("Checksum matched")

+    Path(tag).mkdir()
     # Extract archive
     if host == 'win64':
         try:
```

Code after:


def download_from_url(url, archive):
    last_print_time = time.time()

    def progress_hook(progress_bytes, total_size):
        nonlocal last_print_time
        now = time.time()
        percent = min(100, (progress_bytes * 100) / total_size)
        bar_length = 40
        filled_length = int(bar_length * percent / 100)
        bar = '#' * filled_length + '-' * (bar_length - filled_length)
        if now - last_print_time >= 1 or percent >= 100:
            print(f'\rDownloading: [{bar}] {percent:.1f}%', flush=True, end="")
            last_print_time = now

    with urllib.request.urlopen(url) as response:
        if response.status != 200:
            raise RuntimeError(f"HTTP request failed with status code: {response.status}")

        total_size = int(response.getheader('Content-Length', 0))
        progress_bytes = 0

        with open(archive, 'wb') as file:
            while True:
                chunk = response.read(8192)
                if not chunk:
                    break
                file.write(chunk)
                progress_bytes += len(chunk)
                progress_hook(progress_bytes, total_size)

    print('\n', flush=True, end="") # Flush to avoid error output on the same line.


def download_binary(tag, args) -> int:
    if Path(tag).is_dir():
        if not args.remove_dir:
            print('Using cached {}'.format(tag))
            return 0
        shutil.rmtree(tag)

    bin_path = 'bin/bitcoin-core-{}'.format(tag[1:])

    match = re.compile('v(.*)(rc[0-9]+)$').search(tag)
    if match:
        bin_path = 'bin/bitcoin-core-{}/test.{}'.format(
            match.group(1), match.group(2))

    host = args.host
    if tag < "v23" and host in ["x86_64-apple-darwin", "arm64-apple-darwin"]: