Skip to content

Commit

Permalink
Merge bitcoin/bitcoin#29034: test: detect OS in functional tests cons…
Browse files Browse the repository at this point in the history
…istently using `platform.system()`

878d914 doc: test: mention OS detection preferences in style guideline (Sebastian Falbesoner)
4c65ac9 test: detect OS consistently using `platform.system()` (Sebastian Falbesoner)
37324ae test: use `skip_if_platform_not_linux` helper where possible (Sebastian Falbesoner)

Pull request description:

  There are at least three ways to detect the operating system in Python3:
  - `os.name` (https://docs.python.org/3.9/library/os.html#os.name)
  - `sys.platform` (https://docs.python.org/3.9/library/sys.html#sys.platform)
  - `platform.system()` (https://docs.python.org/3.9/library/platform.html#platform.system)

  We are currently using all of them in functional tests (both in individual tests and shared test framework code), which seems a bit messy. This PR consolidates into using `platform.system()`, as it appears to be one most consistent and easy to read (see also [IRC discussion](https://bitcoin-irc.chaincode.com/bitcoin-core-dev/2023-12-08#989301;) and table below). `sys.platform` is inconsistent as it has the major version number encoded for BSD systems, which doesn't make much sense for e.g. OpenBSD, where there is no concept of major versions, but instead the version is simply increased by 0.1 on each release.

  Note that `os.name` is still useful to detect whether we are running a POSIX system (see `BitcoinTestFramework.skip_if_platform_not_posix`), so for this use-case it is kept as only exception. The following table shows values for common operating systems, found via
  ```
  $ python3 -c "import os; import sys; import platform; print(os.name, sys.platform, platform.system())"
  ```

  |     OS       | os.name | sys.platform | platform.system()  |
  |--------------|---------|--------------|--------------------|
  | Linux 6.2.0  |  posix  |   linux      |      Linux         |
  | MacOS*       |  posix  |   darwin     |      Darwin        |
  | OpenBSD 7.4  |  posix  |   openbsd7   |      OpenBSD       |
  | Windows*     |  nt     |   win32      |      Windows       |

  \* = I neither have a MacOS nor a Windows machine available, so I extracted the values from documentation and our current code. Also I'm relying on CI for testing the relevant code-paths. Having reviewers to this this locally would be very appreciated, if this gets Concept ACKed.

ACKs for top commit:
  kevkevinpal:
    ACK [878d914](bitcoin/bitcoin@878d914)
  achow101:
    ACK 878d914
  hebasto:
    ACK 878d914, I have reviewed the code and it looks OK.
  pablomartin4btc:
    tACK 878d914

Tree-SHA512: 24513d493e47f572028c843260b81c47c2c29bfb701991050255c9f9529cd19065ecbc7b3b6e15619da7f3f608b4825c345ce6fee30d8fd1eaadbd08cff400fc
  • Loading branch information
achow101 committed Jan 11, 2024
2 parents dff6d18 + 878d914 commit bb6de1b
Show file tree
Hide file tree
Showing 12 changed files with 35 additions and 34 deletions.
4 changes: 4 additions & 0 deletions test/functional/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ don't have test cases for.
`set_test_params()`, `add_options()` and `setup_xxxx()` methods at the top of
the subclass, then locally-defined helper methods, then the `run_test()` method.
- Use `f'{x}'` for string formatting in preference to `'{}'.format(x)` or `'%s' % x`.
- Use `platform.system()` for detecting the running operating system and `os.name` to
check whether it's a POSIX system (see also the `skip_if_platform_not_{linux,posix}`
methods in the `BitcoinTestFramework` class, which can be used to skip a whole test
depending on the platform).

#### Naming guidelines

Expand Down
10 changes: 3 additions & 7 deletions test/functional/feature_bind_extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
that bind happens on the expected ports.
"""

import sys

from test_framework.netutil import (
addr_to_hex,
get_bind_addrs,
)
from test_framework.test_framework import (
BitcoinTestFramework,
SkipTest,
)
from test_framework.util import (
assert_equal,
Expand All @@ -32,12 +29,11 @@ def set_test_params(self):
self.bind_to_localhost_only = False
self.num_nodes = 2

def setup_network(self):
def skip_test_if_missing_module(self):
# Due to OS-specific network stats queries, we only run on Linux.
self.log.info("Checking for Linux")
if not sys.platform.startswith('linux'):
raise SkipTest("This test can only be run on Linux.")
self.skip_if_platform_not_linux()

def setup_network(self):
loopback_ipv4 = addr_to_hex("127.0.0.1")

# Start custom ports by reusing unused p2p ports
Expand Down
6 changes: 3 additions & 3 deletions test/functional/feature_config_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import os
from pathlib import Path
import platform
import re
import sys
import tempfile
import time

Expand Down Expand Up @@ -116,7 +116,7 @@ def test_config_file_parser(self):
def test_config_file_log(self):
# Disable this test for windows currently because trying to override
# the default datadir through the environment does not seem to work.
if sys.platform == "win32":
if platform.system() == "Windows":
return

self.log.info('Test that correct configuration path is changed when configuration file changes the datadir')
Expand Down Expand Up @@ -339,7 +339,7 @@ def test_ignored_conf(self):
def test_ignored_default_conf(self):
# Disable this test for windows currently because trying to override
# the default datadir through the environment does not seem to work.
if sys.platform == "win32":
if platform.system() == "Windows":
return

self.log.info('Test error is triggered when bitcoin.conf in the default data directory sets another datadir '
Expand Down
4 changes: 2 additions & 2 deletions test/functional/feature_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Stress tests related to node initialization."""
import os
from pathlib import Path
import platform
import shutil

from test_framework.test_framework import BitcoinTestFramework, SkipTest
Expand Down Expand Up @@ -36,7 +36,7 @@ def run_test(self):
# and other approaches (like below) don't work:
#
# os.kill(node.process.pid, signal.CTRL_C_EVENT)
if os.name == 'nt':
if platform.system() == 'Windows':
raise SkipTest("can't SIGTERM on Windows")

self.stop_node(0)
Expand Down
9 changes: 5 additions & 4 deletions test/functional/feature_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the -alertnotify, -blocknotify and -walletnotify options."""
import os
import platform

from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
from test_framework.descriptors import descsum_create
Expand All @@ -14,13 +15,13 @@

# Linux allow all characters other than \x00
# Windows disallow control characters (0-31) and /\?%:|"<>
FILE_CHAR_START = 32 if os.name == 'nt' else 1
FILE_CHAR_START = 32 if platform.system() == 'Windows' else 1
FILE_CHAR_END = 128
FILE_CHARS_DISALLOWED = '/\\?%*:|"<>' if os.name == 'nt' else '/'
FILE_CHARS_DISALLOWED = '/\\?%*:|"<>' if platform.system() == 'Windows' else '/'
UNCONFIRMED_HASH_STRING = 'unconfirmed'

def notify_outputname(walletname, txid):
return txid if os.name == 'nt' else f'{walletname}_{txid}'
return txid if platform.system() == 'Windows' else f'{walletname}_{txid}'


class NotificationsTest(BitcoinTestFramework):
Expand Down Expand Up @@ -181,7 +182,7 @@ def expect_wallet_notify(self, tx_details):
# Universal newline ensures '\n' on 'nt'
assert_equal(text[-1], '\n')
text = text[:-1]
if os.name == 'nt':
if platform.system() == 'Windows':
# On Windows, echo as above will append a whitespace
assert_equal(text[-1], ' ')
text = text[:-1]
Expand Down
3 changes: 2 additions & 1 deletion test/functional/feature_remove_pruned_files_on_startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test removing undeleted pruned blk files on startup."""

import platform
import os
from test_framework.test_framework import BitcoinTestFramework

Expand Down Expand Up @@ -32,7 +33,7 @@ def run_test(self):
self.nodes[0].pruneblockchain(600)

# Windows systems will not remove files with an open fd
if os.name != 'nt':
if platform.system() != 'Windows':
assert not os.path.exists(blk0)
assert not os.path.exists(rev0)
assert not os.path.exists(blk1)
Expand Down
11 changes: 4 additions & 7 deletions test/functional/rpc_bind.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test running bitcoind with the -rpcbind and -rpcallowip options."""

import sys

from test_framework.netutil import all_interfaces, addr_to_hex, get_bind_addrs, test_ipv6_local
from test_framework.test_framework import BitcoinTestFramework, SkipTest
from test_framework.util import assert_equal, assert_raises_rpc_error, get_rpc_proxy, rpc_port, rpc_url
Expand All @@ -17,6 +15,10 @@ def set_test_params(self):
self.num_nodes = 1
self.supports_cli = False

def skip_test_if_missing_module(self):
# due to OS-specific network stats queries, this test works only on Linux
self.skip_if_platform_not_linux()

def setup_network(self):
self.add_nodes(self.num_nodes, None)

Expand Down Expand Up @@ -61,14 +63,9 @@ def run_allowip_test(self, allow_ips, rpchost, rpcport):
self.stop_nodes()

def run_test(self):
# due to OS-specific network stats queries, this test works only on Linux
if sum([self.options.run_ipv4, self.options.run_ipv6, self.options.run_nonloopback]) > 1:
raise AssertionError("Only one of --ipv4, --ipv6 and --nonloopback can be set")

self.log.info("Check for linux")
if not sys.platform.startswith('linux'):
raise SkipTest("This test can only be run on linux.")

self.log.info("Check for ipv6")
have_ipv6 = test_ipv6_local()
if not have_ipv6 and not (self.options.run_ipv4 or self.options.run_nonloopback):
Expand Down
3 changes: 2 additions & 1 deletion test/functional/test_framework/p2p.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from collections import defaultdict
from io import BytesIO
import logging
import platform
import struct
import sys
import threading
Expand Down Expand Up @@ -592,7 +593,7 @@ def __init__(self):

NetworkThread.listeners = {}
NetworkThread.protos = {}
if sys.platform == 'win32':
if platform.system() == 'Windows':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
NetworkThread.network_event_loop = asyncio.new_event_loop()

Expand Down
4 changes: 2 additions & 2 deletions test/functional/test_framework/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
import json
import logging
import os
import platform
import re
import subprocess
import tempfile
import time
import urllib.parse
import collections
import shlex
import sys
from pathlib import Path

from .authproxy import (
Expand Down Expand Up @@ -566,7 +566,7 @@ def test_success(cmd):
cmd, shell=True,
stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) == 0

if not sys.platform.startswith('linux'):
if platform.system() != 'Linux':
self.log.warning("Can't profile with perf; only available on Linux platforms")
return None

Expand Down
6 changes: 3 additions & 3 deletions test/functional/test_framework/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import logging
import os
import pathlib
import platform
import re
import sys
import time

from . import coverage
Expand Down Expand Up @@ -414,12 +414,12 @@ def get_temp_default_datadir(temp_dir: pathlib.Path) -> tuple[dict, pathlib.Path
"""Return os-specific environment variables that can be set to make the
GetDefaultDataDir() function return a datadir path under the provided
temp_dir, as well as the complete path it would return."""
if sys.platform == "win32":
if platform.system() == "Windows":
env = dict(APPDATA=str(temp_dir))
datadir = temp_dir / "Bitcoin"
else:
env = dict(HOME=str(temp_dir))
if sys.platform == "darwin":
if platform.system() == "Darwin":
datadir = temp_dir / "Library/Application Support/Bitcoin"
else:
datadir = temp_dir / ".bitcoin"
Expand Down
5 changes: 3 additions & 2 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import configparser
import datetime
import os
import platform
import time
import shutil
import signal
Expand All @@ -42,8 +43,8 @@
CROSS = "x "
CIRCLE = "o "

if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): #type:ignore
if os.name == 'nt':
if platform.system() != 'Windows' or sys.getwindowsversion() >= (10, 0, 14393): #type:ignore
if platform.system() == 'Windows':
import ctypes
kernel32 = ctypes.windll.kernel32 # type: ignore
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
Expand Down
4 changes: 2 additions & 2 deletions test/functional/wallet_multiwallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
from decimal import Decimal
from threading import Thread
import os
import platform
import shutil
import stat
import sys
import time

from test_framework.authproxy import JSONRPCException
Expand Down Expand Up @@ -143,7 +143,7 @@ def wallet_file(name):

# should raise rpc error if wallet path can't be created
err_code = -4 if self.options.descriptors else -1
assert_raises_rpc_error(err_code, "filesystem error:" if sys.platform != 'win32' else "create_directories:", self.nodes[0].createwallet, "w8/bad")
assert_raises_rpc_error(err_code, "filesystem error:" if platform.system() != 'Windows' else "create_directories:", self.nodes[0].createwallet, "w8/bad")

# check that all requested wallets were created
self.stop_node(0)
Expand Down

0 comments on commit bb6de1b

Please sign in to comment.