From dfcb1009fd1034d884c82a4c9289d551ab24ba47 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 5 Jun 2026 14:54:10 +0000 Subject: [PATCH 01/16] Enable sharded concurrent NP and LR tests --- .github/workflows/build.yaml | 158 +----------- cmake/test-helpers.cmake | 30 ++- docs/port-sharding-test-plan.md | 231 ++++++++++++++++++ plugins/http_plugin/test/CMakeLists.txt | 3 +- plugins/http_plugin/test/unit_tests.cpp | 135 ++++++---- tests/CMakeLists.txt | 37 +-- .../performance_test_basic.py | 8 +- tests/TestHarness/Cluster.py | 69 ++++-- tests/TestHarness/Node.py | 50 +++- tests/TestHarness/TestHelper.py | 4 +- .../TransactionGeneratorsLauncher.py | 4 +- tests/TestHarness/WalletMgr.py | 14 +- tests/TestHarness/launcher.py | 4 +- tests/TestHarness/testUtils.py | 61 ++++- tests/auto_bp_gossip_peering_test.py | 10 +- tests/auto_bp_peering_test.py | 6 +- tests/cli_test.py | 18 +- tests/disaster_recovery_2.py | 2 +- tests/http_plugin_test.py | 7 +- tests/lib_advance_test.py | 2 +- tests/nodeop_contrl_c_test.py | 4 +- tests/nodeop_forked_chain_test.py | 11 +- tests/nodeop_late_block_test_shape.json | 2 +- tests/p2p_multiple_listen_test.py | 16 +- tests/p2p_no_blocks_test.py | 16 +- tests/p2p_no_listen_test.py | 9 +- tests/p2p_peer_auth_test.py | 5 +- tests/plugin_http_api_test.py | 15 +- tests/resource_monitor_plugin_test.py | 4 +- tests/ship_client.cpp | 4 +- tests/ship_kill_client_test.py | 8 +- tests/ship_kv_delta_test.py | 5 +- tests/ship_reqs_across_svnn_test.py | 15 +- tests/ship_restart_test.py | 8 +- tests/ship_streamer.cpp | 4 +- tests/ship_streamer_test.py | 24 +- tests/ship_test.py | 13 +- tests/split_blocklog_replay_test.py | 2 +- tests/test_port_shard.hpp | 89 +++++++ 39 files changed, 802 insertions(+), 305 deletions(-) create mode 100644 docs/port-sharding-test-plan.md create mode 100644 tests/test_port_shard.hpp diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d06959871c..afb8aa1571 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -234,6 +234,14 @@ jobs: cd build ctest --output-on-failure -j $(nproc) -LE "(nonparallelizable_tests|long_running_tests)" --timeout 1000 + - name: Run Sharded NP/LR Tests + if: matrix.cfg.name != 'asan' + run: | + cd build + test_jobs="$(nproc)" + echo "Running sharded NP/LR tests with ${test_jobs} jobs" + ctest --output-on-failure -j "${test_jobs}" -L "(nonparallelizable_tests|long_running_tests)" --timeout 2700 + - name: Upload core files from failed tests uses: actions/upload-artifact@v6 if: failure() @@ -244,155 +252,13 @@ jobs: /cores build/Testing/Temporary/ build/TestLogs/ + build/PerformanceHarnessScenarioRunnerLogs/ - name: Check CPU Features run: | awk 'BEGIN {err = 1} /bmi2/ && /adx/ {err = 0} END {exit err}' /proc/cpuinfo build/tools/fsgsbase-enabled - np-tests: - name: NP Tests (${{ matrix.cfg.name }}) - needs: [platform-cache, build-base] - strategy: - fail-fast: false - matrix: - include: - - cfg: {name: 'ubuntu24', base: 'ubuntu24', builddir: 'ubuntu24'} - - cfg: {name: 'ubsan', base: 'ubsan', builddir: 'ubsan'} - - cfg: {name: 'asserton', base: 'asserton', builddir: 'asserton'} - - cfg: {name: 'gcc', base: 'gcc', builddir: 'gcc'} - - runs-on: - group: core-repo-group - - steps: - - name: Login to GHCR - run: echo "${{ secrets.GH_TOKEN_DEV }}" | docker login ghcr.io -u dev-wire --password-stdin - - - name: Cleanup stale containers - run: | - echo "Removing old containers..." - docker ps -aq | xargs -r docker rm -f || true - echo "Cleanup complete." - - - uses: actions/checkout@v5 - - - name: Download builddir - uses: actions/download-artifact@v7 - with: - name: ${{ matrix.cfg.builddir }}-build - - - name: Run NP tests - uses: ./.github/actions/parallel-ctest-containers - with: - container: ${{ fromJSON(needs.platform-cache.outputs.platforms)[matrix.cfg.base].image }} - error-log-paths: '["build/etc", "build/var", "build/sysio-ignition-wd", "build/TestLogs"]' - log-tarball-prefix: ${{ matrix.cfg.name }} - tests-label: nonparallelizable_tests - test-timeout: 1000 - batch-size: '5' - - - name: Export core dumps - if: failure() - run: | - docker run --rm \ - --mount type=bind,source=/var/lib/systemd/coredump,target=/cores,readonly \ - alpine sh -c 'tar -C /cores/ -c .' | tar x || echo "No core dumps found" - - - name: Upload logs from failed tests - if: failure() - uses: actions/upload-artifact@v6 - with: - name: ${{ matrix.cfg.name }}-np-logs - path: | - *-logs.tar.gz - core*.zst - compression-level: 0 - - - name: Cleanup Docker resources - if: always() - run: | - echo "=== Cleaning up Docker resources ===" - JOB_PREFIX="base-${{ github.run_id }}-${{ github.job }}" - echo "Removing containers matching: $JOB_PREFIX*" - docker ps -aq --filter "name=$JOB_PREFIX" | xargs -r docker rm -f || true - - echo "Removing base images matching: baseimage-${{ github.run_id }}-${{ github.job }}*" - docker images --format "{{.Repository}}:{{.Tag}}" | \ - grep "baseimage-${{ github.run_id }}-${{ github.job }}" | \ - xargs -r docker rmi -f || true - - lr-tests: - name: LR Tests (${{matrix.cfg.name}}) - needs: [platform-cache, build-base] - strategy: - fail-fast: false - matrix: - include: - - cfg: {name: 'ubuntu24', base: 'ubuntu24', builddir: 'ubuntu24'} - - cfg: {name: 'ubsan', base: 'ubsan', builddir: 'ubsan'} - - cfg: {name: 'asserton', base: 'asserton', builddir: 'asserton'} - - cfg: {name: 'gcc', base: 'gcc', builddir: 'gcc'} - runs-on: - group: core-repo-group - - steps: - - name: Login to GHCR - run: echo "${{ secrets.GH_TOKEN_DEV }}" | docker login ghcr.io -u dev-wire --password-stdin - - - name: Cleanup stale containers - run: | - echo "Removing old containers..." - docker ps -aq | xargs -r docker rm -f || true - echo "Cleanup complete." - - - uses: actions/checkout@v5 - - - name: Download builddir - uses: actions/download-artifact@v7 - with: - name: ${{matrix.cfg.builddir}}-build - - - name: Run tests in parallel containers - uses: ./.github/actions/parallel-ctest-containers - with: - container: ${{fromJSON(needs.platform-cache.outputs.platforms)[matrix.cfg.base].image}} - error-log-paths: '["build/etc", "build/var", "build/sysio-ignition-wd", "build/TestLogs", "build/PerformanceHarnessScenarioRunnerLogs"]' - log-tarball-prefix: ${{matrix.cfg.name}} - tests-label: long_running_tests - test-timeout: 2700 - batch-size: '5' - - - name: Export core dumps - if: failure() - run: | - echo "Checking for core dumps..." - docker run --rm \ - --mount type=bind,source=/var/lib/systemd/coredump,target=/cores,readonly \ - alpine sh -c 'tar -C /cores/ -c .' | tar x || echo "No core dumps found" - - - name: Upload logs from failed tests - uses: actions/upload-artifact@v6 - if: failure() - with: - name: ${{matrix.cfg.name}}-lr-logs - path: | - *-logs.tar.gz - core*.zst - compression-level: 0 - - - name: Cleanup Docker resources - if: always() - run: | - echo "=== Cleaning up Docker resources ===" - JOB_PREFIX="base-${{ github.run_id }}-${{ github.job }}" - echo "Removing containers matching: $JOB_PREFIX*" - docker ps -aq --filter "name=$JOB_PREFIX" | xargs -r docker rm -f || true - - echo "Removing base images matching: baseimage-${{ github.run_id }}-${{ github.job }}*" - docker images --format "{{.Repository}}:{{.Tag}}" | \ - grep "baseimage-${{ github.run_id }}-${{ github.job }}" | \ - xargs -r docker rmi -f || true # libtester-tests: # name: libtester tests # needs: [platform-cache, build-base, v, package] @@ -494,7 +360,7 @@ jobs: all-passing: name: All Required Tests Passed - needs: [ tests, np-tests, lr-tests ] + needs: [ tests ] if: always() runs-on: ubuntu-latest steps: @@ -505,8 +371,8 @@ jobs: webhook-url: ${{ secrets.WEBHOOK_URL }} notification-type: 1 workflow-name: "Build & Test Workflow" - job-results: "tests:${{ needs.tests.result }} np-tests:${{ needs.np-tests.result }} lr-tests:${{ needs.lr-tests.result }}" + job-results: "tests:${{ needs.tests.result }}" github-context: ${{ toJSON(github) }} - - if: needs.tests.result != 'success' || needs.np-tests.result != 'success' || needs.lr-tests.result != 'success' + - if: needs.tests.result != 'success' run: false diff --git a/cmake/test-helpers.cmake b/cmake/test-helpers.cmake index a7217f62f2..462261bc3e 100644 --- a/cmake/test-helpers.cmake +++ b/cmake/test-helpers.cmake @@ -1,5 +1,19 @@ +set(SYSIO_TEST_PORT_OFFSET_START 100) +set(SYSIO_TEST_PORT_OFFSET_STRIDE 256) + +function(next_test_port_offset out_var) + get_property(next_offset GLOBAL PROPERTY SYSIO_NEXT_TEST_PORT_OFFSET) + if(NOT next_offset) + set(next_offset "${SYSIO_TEST_PORT_OFFSET_START}") + endif() + + math(EXPR next_next_offset "${next_offset} + ${SYSIO_TEST_PORT_OFFSET_STRIDE}") + set_property(GLOBAL PROPERTY SYSIO_NEXT_TEST_PORT_OFFSET ${next_next_offset}) + set(${out_var} ${next_offset} PARENT_SCOPE) +endfunction() + function(setup_test_common) - cmake_parse_arguments(PARSE_ARGV 0 arg "" "NAME;COST;TIMEOUT" "COMMAND") + cmake_parse_arguments(PARSE_ARGV 0 arg "AUTO_PORT_OFFSET;AUTO_LR_PORT_OFFSET" "NAME;COST;TIMEOUT;PORT_OFFSET" "COMMAND") add_test(NAME "${arg_NAME}" COMMAND ${arg_COMMAND} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") @@ -9,6 +23,16 @@ function(setup_test_common) if(arg_TIMEOUT) set_tests_properties("${arg_NAME}" PROPERTIES TIMEOUT ${arg_TIMEOUT}) endif() + if(arg_PORT_OFFSET) + set(test_port_offset ${arg_PORT_OFFSET}) + elseif(arg_AUTO_LR_PORT_OFFSET) + next_test_port_offset(test_port_offset) + elseif(arg_AUTO_PORT_OFFSET) + next_test_port_offset(test_port_offset) + endif() + if(DEFINED test_port_offset) + set_tests_properties("${arg_NAME}" PROPERTIES ENVIRONMENT "SYSIO_TEST_PORT_OFFSET=${test_port_offset}") + endif() endfunction() function(add_p_test) @@ -25,13 +49,13 @@ endfunction() function(add_np_test) cmake_parse_arguments(PARSE_ARGV 0 arg "" "NAME;COST;TIMEOUT" "COMMAND") - setup_test_common(${ARGV}) + setup_test_common(${ARGV} AUTO_PORT_OFFSET) set_property(TEST "${arg_NAME}" PROPERTY LABELS nonparallelizable_tests) endfunction() function(add_lr_test) cmake_parse_arguments(PARSE_ARGV 0 arg "" "NAME;COST;TIMEOUT" "COMMAND") - setup_test_common(${ARGV}) + setup_test_common(${ARGV} AUTO_LR_PORT_OFFSET) set_property(TEST "${arg_NAME}" PROPERTY LABELS long_running_tests) endfunction() diff --git a/docs/port-sharding-test-plan.md b/docs/port-sharding-test-plan.md new file mode 100644 index 0000000000..dc7c60e444 --- /dev/null +++ b/docs/port-sharding-test-plan.md @@ -0,0 +1,231 @@ +# Port Sharding Test Plan + +This document describes the current port sharding scheme used by the Python integration tests. The goal is to let +tests that used to require serialized execution run concurrently without binding the same local listener port. + +Port sharding has two layers: + +1. CTest assigns each sharded test a unique `SYSIO_TEST_PORT_OFFSET`. +2. The test harness maps known hot listener ports into a compact, non-overlapping 256-port range for that offset. + +When `SYSIO_TEST_PORT_OFFSET` is unset or `0`, the harness preserves the historical local ports such as `8888`, +`9876`, and `9899`. + +## CTest Offset Allocation + +CTest offsets are assigned in `cmake/test-helpers.cmake` by one shared allocator: + +```cmake +set(SYSIO_TEST_PORT_OFFSET_START 100) +set(SYSIO_TEST_PORT_OFFSET_STRIDE 256) +``` + +Both `add_np_test()` and `add_lr_test()` call the same allocator, so `nonparallelizable_tests` and +`long_running_tests` cannot receive duplicate offsets during the same CMake configure. + +The generated sequence is compact and deterministic: + +```text +100, 356, 612, 868, 1124, ... +``` + +Each offset reserves one 256-port shard. The allocator is global instead of label-specific, which avoids the older +failure mode where an NP test and an LR test could both receive offset `1000`. + +## Harness Port Mapping + +All test-authored local listener ports should go through `Utils.shardPort(port)` or through a `Cluster` / +`WalletMgr` helper that already calls it. + +For nonzero offsets, the harness computes: + +```text +shard_base = 8888 + SYSIO_TEST_PORT_OFFSET +``` + +Known hot ports are then placed into fixed slots inside the 256-port shard: + +| Raw port or range | Use | Sharded port | +|---|---|---| +| `9899..9999` | wallet ports | `shard_base + 0..99` | +| `8788` | BIOS HTTP helper port | `shard_base + 100` | +| `7899` | SHiP / explicit service port | `shard_base + 150` | +| `8080` | state history endpoint default | `shard_base + 151` | +| `9011` | alternate explicit service port | `shard_base + 152` | +| `9776..9822` | alternate P2P/listener ports | `shard_base + 153..199` | +| `8888` | normal node HTTP base | `shard_base + 200` | +| `9876..9898` | normal node P2P base/range | `shard_base + 225..247` | + +Ports that are not in the compact map fall back to `port + SYSIO_TEST_PORT_OFFSET`. That fallback preserves old +manual sharding behavior, but new listener ports should be added to the compact map when they are part of the NP/LR +concurrent test surface. + +The C++ SHiP test clients use the same compact mapping through `tests/test_port_shard.hpp`. Keep that helper in sync +with `Utils.shardPort()` whenever the compact map changes. Otherwise Python may start `nodeop` on the compact +state-history endpoint while `ship_client` or `ship_streamer` still tries to connect to `8080 + offset`. + +The performance harness also has non-port shared state. Its timestamped artifact directory includes the target TPS, +`SYSIO_TEST_PORT_OFFSET`, and PID so concurrent `performance_test_basic_*` runs do not scrape each other's +`trxGenLogs`. + +## Why The Slots Do Not Collide + +Adjacent CTest shards differ by `256`. The compact mapping only uses slots `0..247` today, so no mapped hot listener +from one test can overlap the mapped hot listener ports from the next test. + +For example, with offsets `100` and `356`: + +| Raw port | Offset `100` | Offset `356` | +|---|---:|---:| +| wallet base `9899` | `8988` | `9244` | +| BIOS HTTP `8788` | `9088` | `9344` | +| SHiP `7899` | `9138` | `9394` | +| node HTTP `8888` | `9188` | `9444` | +| node P2P `9876` | `9213` | `9469` | + +The first shard's compact hot range ends before the second shard starts. + +## Ephemeral Port Consideration + +The allocator starts low and uses compact 256-port strides so hot listener ports stay below the OS ephemeral range for +as many tests as possible. On the current Linux runner, `/proc/sys/net/ipv4/ip_local_port_range` starts at `32768`. + +With the current combined NP/LR test list, the highest assigned offset observed during CTest metadata validation was +`22884`. The highest compact hot listener port is therefore: + +```text +8888 + 22884 + 255 = 32027 +``` + +That stays below `32768`, leaving test listener ports out of the default ephemeral allocation range on that runner. + +## Developer Usage + +Run an individual test with an isolated port range by setting `SYSIO_TEST_PORT_OFFSET`: + +```bash +cd build/codex-system-contracts +SYSIO_TEST_PORT_OFFSET=100 python3 tests/nodeop_run_test.py +``` + +When launching tests manually at the same time, choose offsets separated by at least `256`: + +```bash +SYSIO_TEST_PORT_OFFSET=100 python3 tests/nodeop_run_test.py +SYSIO_TEST_PORT_OFFSET=356 python3 tests/http_plugin_test.py +``` + +New test endpoints should use one of these patterns: + +- `Utils.shardPort()` for an explicit listener port. +- `Cluster.getHttpEndpoint(node_id)` for node HTTP endpoints. +- `Cluster.getNodeP2pEndpoint(node_id)` for node P2P endpoints. +- `Cluster.getBiosP2pEndpoint()` for the BIOS P2P endpoint. +- `sysio::testing::shard_port()` in C++ test helpers or test binaries. + +Do not hardcode shifted port numbers in tests. Keep the raw historical port in the test and let the harness assign the +correct shard. + +## Validation Commands + +Check that combined NP/LR CTest metadata has no duplicate shard offsets: + +```bash +ctest --test-dir build/codex-system-contracts -N -V -L 'nonparallelizable_tests|long_running_tests' \ + | rg 'SYSIO_TEST_PORT_OFFSET=' \ + | sed 's/.*=//' \ + | sort -n \ + | uniq -d +``` + +Summarize the current offset range and highest compact hot listener port: + +```bash +python3 - <<'PY' +import re +import subprocess + +out = subprocess.check_output([ + "ctest", + "--test-dir", + "build/codex-system-contracts", + "-N", + "-V", + "-L", + "nonparallelizable_tests|long_running_tests", +], text=True) + +offsets = [int(x) for x in re.findall(r"SYSIO_TEST_PORT_OFFSET=(\d+)", out)] +print("count", len(offsets)) +print("duplicates", len(offsets) - len(set(offsets))) +print("min_offset", min(offsets)) +print("max_offset", max(offsets)) +print("max_compact_hot_port", 8888 + max(offsets) + 255) +PY +``` + +Run a collision-sensitive long-running subset concurrently: + +```bash +ctest --test-dir build/codex-system-contracts -j3 -L long_running_tests \ + -R 'ship_streamer_test|ship_streamer_if_fetch_finality_data_test|auto_bp_gossip_peering_test' \ + --output-on-failure --timeout 1800 +``` + +Run the full NP/LR set with normal high concurrency: + +```bash +ctest --test-dir build/codex-system-contracts -j25 -L 'nonparallelizable_tests|long_running_tests' \ + --output-on-failure --timeout 1800 +``` + +Stress all NP/LR tests at once to look for immediate listener collisions: + +```bash +ctest --test-dir build/codex-system-contracts -j90 -L 'nonparallelizable_tests|long_running_tests' \ + --output-on-failure --timeout 1800 +``` + +Then scan the log for collision signatures: + +```bash +rg 'Address already in use|bind|Failed to bind|Failed to find free port|port .*not available' \ + /tmp/wire-np-lr-j90-*.log +``` + +## Current Validation Results + +The current combined NP/LR metadata assigns 90 offsets with no duplicates. The observed offset range is: + +```text +min_offset = 100 +max_offset = 22884 +max_compact_hot_port = 32027 +``` + +`-j25` full NP/LR validation passed: + +```text +100% tests passed, 0 tests failed out of 90 +Total Test time (real) = 1047.14 sec +``` + +`-j90` stress validation did not find port-collision signatures. It completed with 86/90 tests passing. The failures +were overload/timing/resource-saturation failures, not listener collisions: + +| Test | Observed failure | +|---|---| +| `cli_test` | `node doesn't appear to be running` after node startup under full-suite load | +| `separate_prod_fin_test` | `tx_cpu_usage_exceeded` while publishing `sysio.system` | +| `terminate-scenarios-test-resync` | `Block production handover failed` | +| `nodeop_read_terminate_at_block_lr_test` | block progress assertion: end block was not greater than terminate block | + +The collision-sensitive SHiP tests and performance harness tests passed under `-j90`, which is the strongest current +evidence that port sharding itself is working. + +## Known Non-Port Limits + +Port sharding removes listener collisions; it does not make every NP/LR test safe under unbounded machine load. +At `-j90`, many tests bootstrap chains, publish large contracts, run transaction generators, and start many `nodeop` +processes at once. The observed failures are consistent with CPU and timing pressure. Treat `-j90` as a collision +stress probe, not as the expected CI concurrency level. diff --git a/plugins/http_plugin/test/CMakeLists.txt b/plugins/http_plugin/test/CMakeLists.txt index 2584588b65..f3be3b68bd 100644 --- a/plugins/http_plugin/test/CMakeLists.txt +++ b/plugins/http_plugin/test/CMakeLists.txt @@ -8,6 +8,7 @@ target_link_libraries( http_plugin_unit_tests ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) target_include_directories( http_plugin_unit_tests PUBLIC - ${CMAKE_SOURCE_DIR}/plugins/http_plugin/include ) + ${CMAKE_SOURCE_DIR}/plugins/http_plugin/include + ${CMAKE_SOURCE_DIR}/tests ) add_np_test( NAME http_plugin_unit_tests COMMAND http_plugin_unit_tests ) diff --git a/plugins/http_plugin/test/unit_tests.cpp b/plugins/http_plugin/test/unit_tests.cpp index e4728572a8..0294c3d986 100644 --- a/plugins/http_plugin/test/unit_tests.cpp +++ b/plugins/http_plugin/test/unit_tests.cpp @@ -19,6 +19,8 @@ #include #include +#include "test_port_shard.hpp" + namespace bu = boost::unit_test; using std::string; @@ -31,6 +33,25 @@ namespace http = beast::http; // from namespace net = boost::asio; // from using tcp = net::ip::tcp; // from +namespace { +constexpr uint16_t default_http_port = 8888; +constexpr uint16_t category_rw_port = 8889; +constexpr uint16_t category_ro_port = 8890; +constexpr uint16_t bytes_in_flight_port = 8891; +constexpr uint16_t requests_in_flight_port = 8892; +constexpr uint16_t ipv6_probe_port = 9999; + +/** Return the string form of a base port after applying this test's shard. */ +std::string test_port(uint16_t port) { + return std::to_string(sysio::testing::shard_port(port)); +} + +/** Return host:port with this test's port shard applied. */ +std::string test_endpoint(const std::string& host, uint16_t port) { + return host + ":" + test_port(port); +} +} // namespace + // ------------------------------------------------------------------------- // this class handles some basic http requests. // ------------------------------------------------------------------------- @@ -300,9 +321,9 @@ struct http_plugin_test_fixture { // ------------------------------------------------------------------------- BOOST_FIXTURE_TEST_CASE(http_plugin_unit_tests, http_plugin_test_fixture) { - const uint16_t default_port{8888}; - const char* port = "8888"; - const char* host = "127.0.0.1"; + const uint16_t default_port = sysio::testing::shard_port(default_http_port); + const std::string port = test_port(default_http_port); + const char* host = "127.0.0.1"; http_plugin::set_defaults({.default_unix_socket_path = "", .default_http_port = default_port, .server_header = "/"}); @@ -392,33 +413,41 @@ class app_log { } return true; } + + boost::test_tools::predicate_result contains(const std::string& str) const { return contains(str.c_str()); } }; BOOST_AUTO_TEST_CASE(invalid_category_addresses) { const char* test_name = bu::framework::current_test_case().p_name->c_str(); + const std::string localhost_rw = test_endpoint("localhost", category_rw_port); + const std::string loopback_rw = test_endpoint("127.0.0.1", category_rw_port); + const std::string chain_ro_localhost = "chain_ro," + localhost_rw; + const std::string chain_ro_loopback = "chain_ro," + loopback_rw; + const std::string chain_rw_localhost = "chain_rw," + localhost_rw; + const std::string node_localhost = "node," + localhost_rw; + const std::string unable_to_listen_msg = "unable to listen to port " + test_port(category_rw_port); BOOST_TEST(app_log({test_name, "--plugin=sysio::http_plugin", "--http-server-address", - "http-category-address", "--http-category-address", "chain_ro,localhost:8889"}) + "http-category-address", "--http-category-address", chain_ro_localhost.c_str()}) .contains("--plugin=sysio::chain_api_plugin is required")); - BOOST_TEST(app_log({test_name, "--plugin=sysio::chain_api_plugin", "--http-category-address", - "chain_ro,localhost:8889"}) + BOOST_TEST(app_log({test_name, "--plugin=sysio::chain_api_plugin", "--http-category-address", chain_ro_localhost.c_str()}) .contains("http-server-address must be set as `http-category-address`")); BOOST_TEST(app_log({test_name, "--plugin=sysio::chain_api_plugin", "--http-server-address", "http-category-address", "--unix-socket-path", "/tmp/tmp.sock", - "--http-category-address", "chain_ro,localhost:8889"}) + "--http-category-address", chain_ro_localhost.c_str()}) .contains("`unix-socket-path` must be left unspecified")); BOOST_TEST(app_log({test_name, "--plugin=sysio::chain_api_plugin", "--http-server-address", - "http-category-address", "--http-category-address", "node,localhost:8889"}) + "http-category-address", "--http-category-address", node_localhost.c_str()}) .contains("invalid category name")); BOOST_TEST(app_log({test_name, "--plugin=sysio::chain_api_plugin", "--http-server-address", - "http-category-address", "--http-category-address", "chain_ro,127.0.0.1:8889", - "--http-category-address", "chain_rw,localhost:8889"}) - .contains("unable to listen to port 8889")); + "http-category-address", "--http-category-address", chain_ro_loopback.c_str(), + "--http-category-address", chain_rw_localhost.c_str()}) + .contains(unable_to_listen_msg)); } struct http_response_for { @@ -466,6 +495,17 @@ struct http_response_for { BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { fc::temp_directory dir; auto data_dir = dir.path() / "data"; + const std::string ro_port = test_port(category_ro_port); + const std::string rw_port = test_port(category_rw_port); + const std::string ro_loopback = test_endpoint("127.0.0.1", category_ro_port); + const std::string ro_localhost = test_endpoint("localhost", category_ro_port); + const std::string rw_loopback = test_endpoint("127.0.0.1", category_rw_port); + const std::string rw_any = ":" + rw_port; + const std::string chain_ro = "chain_ro," + ro_loopback; + const std::string chain_rw = "chain_rw," + rw_any; + const std::string net_ro = "net_ro," + ro_loopback; + const std::string net_rw = "net_rw," + rw_any; + const std::string ipv6_rw = "[::1]:" + rw_port; // clang-format off auto http_plugin = init({bu::framework::current_test_case().p_name->c_str(), @@ -474,10 +514,10 @@ BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { "--plugin=sysio::net_api_plugin", "--plugin=sysio::producer_api_plugin", "--http-server-address", "http-category-address", - "--http-category-address", "chain_ro,127.0.0.1:8890", - "--http-category-address", "chain_rw,:8889", - "--http-category-address", "net_ro,127.0.0.1:8890", - "--http-category-address", "net_rw,:8889", + "--http-category-address", chain_ro.c_str(), + "--http-category-address", chain_rw.c_str(), + "--http-category-address", net_ro.c_str(), + "--http-category-address", net_rw.c_str(), "--http-category-address", "producer_ro,./producer_ro.sock", "--http-category-address", "producer_rw,../producer_rw.sock" }); @@ -524,13 +564,13 @@ BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { std::string world_string = "\"world!\""; - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/node/hello").body(), world_string); - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/node/hello").body(), world_string); + BOOST_CHECK_EQUAL(http_response_for(ro_loopback.c_str(), "/v1/node/hello").body(), world_string); + BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/node/hello").body(), world_string); bool ip_v6_enabled = [] { try { net::io_context ioc; - tcp::socket s(ioc, tcp::endpoint{net::ip::make_address("::1"), 9999}); + tcp::socket s(ioc, tcp::endpoint{net::ip::make_address("::1"), sysio::testing::shard_port(ipv6_probe_port)}); return true; } catch (...) { return false; @@ -538,27 +578,27 @@ BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { }(); if (ip_v6_enabled) { - BOOST_CHECK_EQUAL(http_response_for("[::1]:8889", "/v1/node/hello").body(), world_string); + BOOST_CHECK_EQUAL(http_response_for(ipv6_rw.c_str(), "/v1/node/hello").body(), world_string); } - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/chain_ro/hello").body(), world_string); - BOOST_CHECK_EQUAL(http_response_for("localhost:8890", "/v1/chain_ro/hello").status(), http::status::bad_request); - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/net_ro/hello").body(), world_string); - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/chain_rw/hello").status(), http::status::not_found); - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/net_rw/hello").status(), http::status::not_found); + BOOST_CHECK_EQUAL(http_response_for(ro_loopback.c_str(), "/v1/chain_ro/hello").body(), world_string); + BOOST_CHECK_EQUAL(http_response_for(ro_localhost.c_str(), "/v1/chain_ro/hello").status(), http::status::bad_request); + BOOST_CHECK_EQUAL(http_response_for(ro_loopback.c_str(), "/v1/net_ro/hello").body(), world_string); + BOOST_CHECK_EQUAL(http_response_for(ro_loopback.c_str(), "/v1/chain_rw/hello").status(), http::status::not_found); + BOOST_CHECK_EQUAL(http_response_for(ro_loopback.c_str(), "/v1/net_rw/hello").status(), http::status::not_found); - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/chain_ro/hello").status(), http::status::not_found); - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/net_ro/hello").status(), http::status::not_found); - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/chain_rw/hello").body(), world_string); - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/net_rw/hello").body(), world_string); + BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/chain_ro/hello").status(), http::status::not_found); + BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/net_ro/hello").status(), http::status::not_found); + BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/chain_rw/hello").body(), world_string); + BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/net_rw/hello").body(), world_string); BOOST_CHECK_EQUAL(http_response_for(data_dir / "./producer_ro.sock", "/v1/producer_ro/hello").body(), world_string); BOOST_CHECK_EQUAL(http_response_for(data_dir / "../producer_rw.sock", "/v1/producer_rw/hello").body(), world_string); - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8890", "/v1/node/get_supported_apis").body(), + BOOST_CHECK_EQUAL(http_response_for(ro_loopback.c_str(), "/v1/node/get_supported_apis").body(), R"({"apis":["/v1/chain_ro/hello","/v1/net_ro/hello","/v1/node/hello"]})"); - BOOST_CHECK_EQUAL(http_response_for("127.0.0.1:8889", "/v1/node/get_supported_apis").body(), + BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/node/get_supported_apis").body(), R"({"apis":["/v1/chain_rw/hello","/v1/net_rw/hello","/v1/node/hello"]})"); } @@ -570,16 +610,25 @@ bool on_loopback(std::initializer_list args){ } BOOST_AUTO_TEST_CASE(test_on_loopback) { + const std::string loopback_default = test_endpoint("127.0.0.1", default_http_port); + const std::string localhost_default = test_endpoint("localhost", default_http_port); + const std::string any_default = ":" + test_port(default_http_port); + const std::string external_default = test_endpoint("example.com", default_http_port); + BOOST_CHECK(on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", "", "--unix-socket-path=a"})); - BOOST_CHECK(on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", "127.0.0.1:8888"})); - BOOST_CHECK(on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", "localhost:8888"})); - BOOST_CHECK(!on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", ":8888"})); - BOOST_CHECK(!on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", "example.com:8888"})); + BOOST_CHECK(on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", loopback_default.c_str()})); + BOOST_CHECK(on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", localhost_default.c_str()})); + BOOST_CHECK(!on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", any_default.c_str()})); + BOOST_CHECK(!on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", external_default.c_str()})); } BOOST_FIXTURE_TEST_CASE(bytes_in_flight, http_plugin_test_fixture) { + const std::string endpoint = test_endpoint("127.0.0.1", bytes_in_flight_port); + const std::string server_address = "--http-server-address=" + endpoint; + const std::string port = test_port(bytes_in_flight_port); + http_plugin* http_plugin = init({"--plugin=sysio::http_plugin", - "--http-server-address=127.0.0.1:8891", + server_address.c_str(), "--http-max-bytes-in-flight-mb=64"}); BOOST_REQUIRE(http_plugin); @@ -602,10 +651,10 @@ BOOST_FIXTURE_TEST_CASE(bytes_in_flight, http_plugin_test_fixture) { //we can't control http_plugin's send buffer, but at least we can control our receive buffer size to help increase // chance of server blocking s.set_option(boost::asio::socket_base::receive_buffer_size(8*1024)); - boost::asio::connect(s, resolver.resolve("127.0.0.1", "8891")); + boost::asio::connect(s, resolver.resolve("127.0.0.1", port)); boost::beast::http::request req(boost::beast::http::verb::get, "/4megabyte", 11); req.keep_alive(true); - req.set(http::field::host, "127.0.0.1:8891"); + req.set(http::field::host, endpoint); boost::beast::http::write(s, req); } }; @@ -676,8 +725,12 @@ BOOST_FIXTURE_TEST_CASE(bytes_in_flight, http_plugin_test_fixture) { } BOOST_FIXTURE_TEST_CASE(requests_in_flight, http_plugin_test_fixture) { + const std::string endpoint = test_endpoint("127.0.0.1", requests_in_flight_port); + const std::string server_address = "--http-server-address=" + endpoint; + const std::string port = test_port(requests_in_flight_port); + http_plugin* http_plugin = init({"--plugin=sysio::http_plugin", - "--http-server-address=127.0.0.1:8892", + server_address.c_str(), "--http-max-in-flight-requests=16"}); BOOST_REQUIRE(http_plugin); @@ -694,10 +747,10 @@ BOOST_FIXTURE_TEST_CASE(requests_in_flight, http_plugin_test_fixture) { auto send_requests = [&](unsigned count) { for(unsigned i = 0; i < count; ++i) { boost::asio::ip::tcp::socket& s = connections.emplace_back(ctx, boost::asio::ip::tcp::v4()); - boost::asio::connect(s, resolver.resolve("127.0.0.1", "8892")); + boost::asio::connect(s, resolver.resolve("127.0.0.1", port)); boost::beast::http::request req(boost::beast::http::verb::get, "/doit", 11); req.keep_alive(true); - req.set(http::field::host, "127.0.0.1:8892"); + req.set(http::field::host, endpoint); boost::beast::http::write(s, req); } }; @@ -752,4 +805,4 @@ BOOST_FIXTURE_TEST_CASE(requests_in_flight, http_plugin_test_fixture) { } //A warning for future tests: destruction of http_plugin_test_fixture sometimes does not destroy http_plugin's listeners. Tests -// added in the future should avoid reusing ports of other tests in http_plugin_unit_tests. \ No newline at end of file +// added in the future should avoid reusing ports of other tests in http_plugin_unit_tests. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1a7575114a..40cd19889d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -133,9 +133,9 @@ endif() #To run plugin_test with all log from blockchain displayed, put --verbose after --, i.e. plugin_test -- --verbose add_p_test(NAME plugin_test COMMAND plugin_test --report_level=detailed --color_output) -add_np_test(NAME nodeop_sanity_test COMMAND tests/nodeop_run_test.py -v --sanity-test ${UNSHARE}) -add_np_test(NAME nodeop_run_test COMMAND tests/nodeop_run_test.py -v ${UNSHARE}) -add_np_test(NAME nodeop_lib_test COMMAND tests/nodeop_lib_test.py -n 4 -p 3 -s ring -v ${UNSHARE}) +add_p_test(NAME nodeop_sanity_test COMMAND tests/nodeop_run_test.py -v --sanity-test ${UNSHARE} PORT_OFFSET 1000) +add_p_test(NAME nodeop_run_test COMMAND tests/nodeop_run_test.py -v ${UNSHARE} PORT_OFFSET 2000) +add_p_test(NAME nodeop_lib_test COMMAND tests/nodeop_lib_test.py -n 4 -p 3 -s ring -v ${UNSHARE} PORT_OFFSET 3000) add_np_test(NAME block_log_util_test COMMAND tests/block_log_util_test.py -v ${UNSHARE}) add_np_test(NAME block_log_retain_blocks_test COMMAND tests/block_log_retain_blocks_test.py -v ${UNSHARE}) @@ -154,7 +154,7 @@ add_np_test(NAME cluster_launcher COMMAND tests/cluster_launcher.py -v ${UNSHARE add_np_test(NAME transition_to_if COMMAND tests/transition_to_if.py -v ${UNSHARE}) add_np_test(NAME disaster_recovery COMMAND tests/disaster_recovery.py -v ${UNSHARE}) -add_np_test(NAME disaster_recovery_2 COMMAND tests/disaster_recovery_2.py -v ${UNSHARE}) +add_p_test(NAME disaster_recovery_2 COMMAND tests/disaster_recovery_2.py -v ${UNSHARE} PORT_OFFSET 6000) add_np_test(NAME disaster_recovery_3 COMMAND tests/disaster_recovery_3.py -v ${UNSHARE}) add_np_test(NAME production_pause_max_rev_blks_test COMMAND tests/production_pause_max_rev_blks_test.py -v ${UNSHARE}) add_np_test(NAME production_pause_vote_timeout COMMAND tests/production_pause_vote_timeout.py -v ${UNSHARE}) @@ -164,7 +164,7 @@ add_np_test(NAME ship_reqs_across_svnn_test COMMAND tests/ship_reqs_across_svnn_ add_np_test(NAME ship_restart_test COMMAND tests/ship_restart_test.py -v ${UNSHARE}) add_np_test(NAME ship_test COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 ${UNSHARE}) add_np_test(NAME ship_test_unix COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 ${UNSHARE} --unix-socket) -add_np_test(NAME ship_kv_delta_test COMMAND tests/ship_kv_delta_test.py -v ${UNSHARE}) +add_p_test(NAME ship_kv_delta_test COMMAND tests/ship_kv_delta_test.py -v ${UNSHARE} PORT_OFFSET 7000) add_np_test(NAME get_kv_rows_test COMMAND tests/get_kv_rows_test.py -v ${UNSHARE}) add_lr_test(NAME ship_streamer_test COMMAND tests/ship_streamer_test.py -v --num-clients 10 ${UNSHARE}) @@ -211,11 +211,11 @@ add_p_test(NAME version-label-test COMMAND tests/version-label.sh "v${VERSION_FU add_p_test(NAME full-version-label-test COMMAND tests/full-version-label.sh "v${VERSION_FULL}" ${CMAKE_SOURCE_DIR}) add_np_test(NAME nested_container_multi_index_test COMMAND tests/nested_container_multi_index_test.py -n 2) -add_np_test(NAME p2p_multiple_listen_test COMMAND tests/p2p_multiple_listen_test.py -v ${UNSHARE}) -add_np_test(NAME p2p_no_listen_test COMMAND tests/p2p_no_listen_test.py -v ${UNSHARE}) +add_p_test(NAME p2p_multiple_listen_test COMMAND tests/p2p_multiple_listen_test.py -v ${UNSHARE} PORT_OFFSET 8000) +add_p_test(NAME p2p_no_listen_test COMMAND tests/p2p_no_listen_test.py -v ${UNSHARE} PORT_OFFSET 13000) add_lr_test(NAME p2p_sync_throttle_test COMMAND tests/p2p_sync_throttle_test.py -v -d 2 ${UNSHARE}) -add_np_test(NAME p2p_no_blocks_if_test COMMAND tests/p2p_no_blocks_test.py -v -d 2 ${UNSHARE}) -add_np_test(NAME p2p_peer_auth_test COMMAND tests/p2p_peer_auth_test.py -v ${UNSHARE}) +add_p_test(NAME p2p_no_blocks_if_test COMMAND tests/p2p_no_blocks_test.py -v -d 2 ${UNSHARE} PORT_OFFSET 9000) +add_p_test(NAME p2p_peer_auth_test COMMAND tests/p2p_peer_auth_test.py -v ${UNSHARE} PORT_OFFSET 4000) add_np_test(NAME p2p_peer_scoring_test COMMAND tests/p2p_peer_scoring_test.py -v ${UNSHARE}) # needs iproute-tc or iproute2 depending on platform @@ -224,11 +224,11 @@ add_np_test(NAME p2p_peer_scoring_test COMMAND tests/p2p_peer_scoring_test.py -v # This test is too much for CI/CD machines. We do run it with fewer nodes as a nonparallelizable_tests above #add_lr_test(NAME distributed_transactions_lr_test COMMAND tests/distributed-transactions-test.py -d 2 -p 21 -n 21 -v) -add_lr_test(NAME nodeop_forked_chain_lr_test COMMAND tests/nodeop_forked_chain_test.py -v --wallet-port 9901 ${UNSHARE}) +add_lr_test(NAME nodeop_forked_chain_lr_test COMMAND tests/nodeop_forked_chain_test.py -v ${UNSHARE}) -add_np_test(NAME nodeop_contrl_c_test COMMAND tests/nodeop_contrl_c_test.py -v --wallet-port 9901 ${UNSHARE}) +add_p_test(NAME nodeop_contrl_c_test COMMAND tests/nodeop_contrl_c_test.py -v ${UNSHARE} PORT_OFFSET 14000) -add_lr_test(NAME nodeop_under_min_avail_ram_lr_test COMMAND tests/nodeop_under_min_avail_ram.py -v --wallet-port 9904 ${UNSHARE}) +add_lr_test(NAME nodeop_under_min_avail_ram_lr_test COMMAND tests/nodeop_under_min_avail_ram.py -v ${UNSHARE}) add_lr_test(NAME nodeop_irreversible_mode_lr_test COMMAND tests/nodeop_irreversible_mode_test.py -v ${UNSHARE}) @@ -244,7 +244,7 @@ add_np_test(NAME metamask_trx_signing_test COMMAND tests/metamask/push_metamask_ add_lr_test(NAME nodeop_startup_catchup_lr_test COMMAND tests/nodeop_startup_catchup.py -v ${UNSHARE}) -add_np_test(NAME nodeop_short_fork_take_over_test COMMAND tests/nodeop_short_fork_take_over_test.py -v --wallet-port 9905 ${UNSHARE}) +add_np_test(NAME nodeop_short_fork_take_over_test COMMAND tests/nodeop_short_fork_take_over_test.py -v ${UNSHARE}) add_np_test(NAME nodeop_extra_packed_data_test COMMAND tests/nodeop_extra_packed_data_test.py -v -p 2 ${UNSHARE}) @@ -264,14 +264,15 @@ add_p_test(NAME sysio_util_bls_test COMMAND tests/sysio_util_bls_test.py) add_p_test(NAME clio_em_key_test COMMAND tests/clio_em_key_test.py) add_p_test(NAME sysio_util_snapshot_info_test COMMAND tests/sysio_util_snapshot_info_test.py) -add_np_test(NAME http_plugin_test COMMAND tests/http_plugin_test.py ${UNSHARE} TIMEOUT 200) +add_p_test(NAME http_plugin_test COMMAND tests/http_plugin_test.py ${UNSHARE} TIMEOUT 200 PORT_OFFSET 5000) -add_np_test(NAME plugin_http_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100) +add_p_test(NAME plugin_http_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100 PORT_OFFSET 10000) -add_np_test(NAME plugin_http_api_test_savanna COMMAND tests/plugin_http_api_test_savanna.py TIMEOUT 340) +add_p_test(NAME plugin_http_api_test_savanna COMMAND tests/plugin_http_api_test_savanna.py TIMEOUT 340 PORT_OFFSET 11000) -add_np_test(NAME plugin_http_category_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100) -set_tests_properties(plugin_http_category_api_test PROPERTIES ENVIRONMENT "PLUGIN_HTTP_TEST_CATEGORY=ON") +add_p_test(NAME plugin_http_category_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100 PORT_OFFSET 12000) +set_tests_properties(plugin_http_category_api_test PROPERTIES ENVIRONMENT + "SYSIO_TEST_PORT_OFFSET=12000;PLUGIN_HTTP_TEST_CATEGORY=ON") add_np_test(NAME trace_plugin_test COMMAND tests/trace_plugin_test.py -v TIMEOUT 400) diff --git a/tests/PerformanceHarness/performance_test_basic.py b/tests/PerformanceHarness/performance_test_basic.py index f56a1833f5..2b80e7c0cd 100755 --- a/tests/PerformanceHarness/performance_test_basic.py +++ b/tests/PerformanceHarness/performance_test_basic.py @@ -85,7 +85,7 @@ class ExtraNodeopArgs: def __str__(self) -> str: args = [] for field in dataclasses.fields(self): - match = re.search("\w*PluginArgs", field.name) + match = re.search(r"\w*PluginArgs", field.name) if match is not None: args.append(f"{getattr(self, field.name)}") return " ".join(args) @@ -204,9 +204,11 @@ def __init__(self, testHelperConfig: TestHelperConfig=TestHelperConfig(), cluste self.testStart = datetime.now(UTC) self.testEnd = self.testStart self.testNamePath = testNamePath + # Keep concurrent CTest runs from sharing performance artifact directories. + logDirUniqueSuffix = f"-{self.ptbConfig.targetTps}-{Utils.getTestPortOffset()}-{os.getpid()}" self.loggingConfig = PerformanceTestBasic.LoggingConfig(logDirBase=Path(self.ptbConfig.logDirRoot)/f"{self.testNamePath}Logs", logDirTimestamp=f"{self.testStart.strftime('%Y-%m-%d_%H-%M-%S')}", - logDirTimestampedOptSuffix = f"-{self.ptbConfig.targetTps}") + logDirTimestampedOptSuffix=logDirUniqueSuffix) self.trxGenLogDirPath = self.loggingConfig.logDirPath/Path("trxGenLogs") self.varLogsDirPath = self.loggingConfig.logDirPath/Path("var") @@ -226,7 +228,7 @@ def __init__(self, testHelperConfig: TestHelperConfig=TestHelperConfig(), cluste self.nodeopLogPath = self.nodeopLogDir/f"node_{str(self.validationNodeId).zfill(2)}"/"stderr.txt" # Setup cluster and its wallet manager - self.walletMgr=WalletMgr(True, port=7899) + self.walletMgr=WalletMgr(True) self.cluster=Cluster(loggingLevel=self.clusterConfig.loggingLevel, loggingLevelDict=self.clusterConfig.loggingDict, nodeopVers=self.clusterConfig.nodeopVers,unshared=self.testHelperConfig.unshared, keepRunning=self.clusterConfig.dontKill, keepLogs=self.clusterConfig.keepLogs) diff --git a/tests/TestHarness/Cluster.py b/tests/TestHarness/Cluster.py index a290a103e2..d4643416ec 100644 --- a/tests/TestHarness/Cluster.py +++ b/tests/TestHarness/Cluster.py @@ -58,12 +58,14 @@ class Cluster(object): __WalletName="MyWallet" __localHost="localhost" __BiosHost="localhost" - __BiosPort=8788 + __BiosPort=Utils.shardPort(8788) __LauncherCmdArr=[] __bootlog="wire_sysio-ignition-wd/bootlog.txt" + __localLaunchPortCheckAttempts=90 + __localLaunchPortCheckSleepSeconds=2 # pylint: disable=too-many-arguments - def __init__(self, localCluster=True, host="localhost", port=8888, walletHost="localhost", walletPort=9899 + def __init__(self, localCluster=True, host="localhost", port=None, walletHost="localhost", walletPort=None , defproduceraPrvtKey=None, defproducerbPrvtKey=None, staging=False, loggingLevel="debug", loggingLevelDict={}, nodeopVers="", unshared=False, keepRunning=False, keepLogs=False): """Cluster container. localCluster [True|False] Is cluster local to host. @@ -88,10 +90,10 @@ def __init__(self, localCluster=True, host="localhost", port=8888, walletHost="l self.wallet=None self.walletMgr=None self.host=host - self.port=port - self.p2pBasePort=9876 + self.port=Utils.shardPort(8888) if port is None else port + self.p2pBasePort=Utils.shardPort(9876) self.walletHost=walletHost - self.walletPort=walletPort + self.walletPort=Utils.shardPort(9899) if walletPort is None else walletPort self.staging=staging self.loggingLevel=loggingLevel self.loggingLevelDict=loggingLevelDict @@ -171,6 +173,31 @@ def setAlternateVersionLabels(self, file): # pylint: disable=too-many-return-statements # pylint: disable=too-many-branches # pylint: disable=too-many-statements + def _shutdownLaunchFailure(self): + """Terminate nodes started by a failed launch without deleting diagnostic logs.""" + if self.keepRunning: + Utils.Print('Cluster launch failed; cluster left running.') + return False + + Utils.Print('Cluster launch failed; shutting down started nodes.') + for node in self.nodes: + node.kill(signal.SIGTERM) + if self.biosNode is not None and (len(self.nodes) == 0 or self.biosNode != self.nodes[0]): + self.biosNode.kill(signal.SIGTERM) + + if self.trxGenLauncher is not None: + self.trxGenLauncher.killAll() + + return False + + def _portsForLocalLaunch(self, totalNodes): + """Return the local HTTP and P2P ports reserved by a cluster launch.""" + ports = set(range(self.port, self.port + totalNodes + 1)) + ports.add(self.port - 100) + ports.update(range(self.p2pBasePort, self.p2pBasePort + totalNodes + 1)) + ports.add(self.p2pBasePort - 100) + return ports + def launch(self, pnodes=1, unstartedNodes=0, totalNodes=1, prodCount=21, topo="mesh", delay=2, onlyBios=False, dontBootstrap=False, totalProducers=None, sharedProducers=0, extraNodeopArgs="", specificExtraNodeopArgs=None, specificNodeopInstances=None, onlySetProds=False, pfSetupPolicy=PFSetupPolicy.FULL, alternateVersionLabelsFile=None, associatedNodeLabels=None, loadSystemContract=True, @@ -247,13 +274,14 @@ def launch(self, pnodes=1, unstartedNodes=0, totalNodes=1, prodCount=21, topo="m self.setAlternateVersionLabels(alternateVersionLabelsFile) - tries = 30 - while not Utils.arePortsAvailable(set(range(self.port, self.port+totalNodes+1))): - Utils.Print("ERROR: Another process is listening on nodeop default port. wait...") + portsToCheck = self._portsForLocalLaunch(totalNodes) + tries = Cluster.__localLaunchPortCheckAttempts + while not Utils.arePortsAvailable(portsToCheck): + Utils.Print("ERROR: Another process is listening on a nodeop launch port. wait...") if tries == 0: return False tries = tries - 1 - time.sleep(2) + time.sleep(Cluster.__localLaunchPortCheckSleepSeconds) loggingLevelDictString = json.dumps(self.loggingLevelDict, separators=(',', ':')) args=(f'-p {pnodes} -n {totalNodes} -d {delay} ' f'-i {datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]} -f {producerFlag} ' @@ -487,17 +515,17 @@ def connectGroup(group, producerNodes, bridgeNodes) : if self.nodes is None or self.startedNodesCount != len(self.nodes): Utils.Print("ERROR: Unable to validate %s instances, expected: %d, actual: %d" % (Utils.SysServerName, self.startedNodesCount, len(self.nodes))) - return False + return self._shutdownLaunchFailure() if not self.biosNode or not Utils.waitForBool(self.biosNode.checkPulse, Utils.systemWaitTimeout): Utils.Print("ERROR: Bios node doesn't appear to be running...") - return False + return self._shutdownLaunchFailure() # ensure cluster node are inter-connected by ensuring everyone has block 1 Utils.Print("Cluster viability smoke test. Validate every cluster node has block 1. ") if not self.waitOnClusterBlockNumSync(1): Utils.Print("ERROR: Cluster doesn't seem to be in sync. Some nodes missing block 1") - return False + return self._shutdownLaunchFailure() if dontBootstrap: Utils.Print("Skipping bootstrap.") @@ -506,14 +534,14 @@ def connectGroup(group, producerNodes, bridgeNodes) : Utils.Print("Bootstrap cluster.") if not self.bootstrap(launcher, self.biosNode, self.startedNodesCount, prodCount + sharedProducers, totalProducers, pfSetupPolicy, onlyBios, onlySetProds, loadSystemContract, activateIF, biosFinalizer, signatureProviderForNonProducer): Utils.Print("ERROR: Bootstrap failed.") - return False + return self._shutdownLaunchFailure() # validate iniX accounts can be retrieved producerKeys=Cluster.parseClusterKeys(totalNodes) if producerKeys is None: Utils.Print("ERROR: Unable to parse cluster info") - return False + return self._shutdownLaunchFailure() def initAccountKeys(account, keys): account.ownerPrivateKey=keys["private"] @@ -703,8 +731,21 @@ def populateWallet(self, accountsCount, wallet, accountNames: list=None, createP return True def getNodeP2pPort(self, nodeId: int): + """Return the sharded P2P port for a non-BIOS node.""" return self.p2pBasePort + nodeId + def getNodeP2pEndpoint(self, nodeId: int): + """Return the sharded P2P endpoint for a non-BIOS node.""" + return f"{self.host}:{self.getNodeP2pPort(nodeId)}" + + def getBiosP2pEndpoint(self): + """Return the sharded P2P endpoint for the BIOS node.""" + return f"{self.host}:{self.p2pBasePort - 100}" + + def getHttpEndpoint(self, nodeId: int): + """Return the sharded HTTP endpoint for a non-BIOS node.""" + return f"{self.host}:{self.port + nodeId}" + def getNode(self, nodeId=0, exitOnError=True): if exitOnError and nodeId >= len(self.nodes): Utils.cmdError("cluster never created node %d" % (nodeId)) diff --git a/tests/TestHarness/Node.py b/tests/TestHarness/Node.py index 30dda03008..54b04c3b17 100644 --- a/tests/TestHarness/Node.py +++ b/tests/TestHarness/Node.py @@ -83,6 +83,44 @@ def __init__(self, host, port, nodeId: int, data_dir: Path, config_dir: Path, cm def __str__(self): return "Host: %s, Port:%d, NodeNum:%s, Pid:%s" % (self.host, self.port, self.nodeId, self.pid) + @staticmethod + def _portFromEndpoint(endpoint): + """Return the numeric TCP port from a nodeop endpoint argument.""" + if endpoint is None: + return None + + portText = endpoint.rsplit(":", 1)[-1].strip("[]") + try: + return int(portText) + except ValueError: + return None + + @staticmethod + def _listenerPortsFromCmd(cmd): + """Return final listener ports declared in a nodeop command line.""" + listenerArgs = { + "--http-server-address", + "--p2p-listen-endpoint", + "--state-history-endpoint", + } + ports = set() + for index, arg in enumerate(cmd): + endpoint = None + if arg in listenerArgs and index + 1 < len(cmd): + endpoint = cmd[index + 1] + else: + for listenerArg in listenerArgs: + prefix = listenerArg + "=" + if arg.startswith(prefix): + endpoint = arg[len(prefix):] + break + + port = Node._portFromEndpoint(endpoint) + if port is not None: + ports.add(port) + + return ports + @staticmethod def __printTransStructureError(trans, context): Utils.Print("ERROR: Failure in expected transaction structure. Missing trans%s." % (context)) @@ -498,12 +536,14 @@ def isNodeAlive(): self.popenProc.wait() self.pid=None - def launchCmd(self, cmd: List[str], data_dir: Path, launch_time: str): + def launchCmd(self, cmd: List[str], data_dir: Path, launch_time: str, waitForPorts=True): + """Launch a nodeop command, optionally waiting for declared listener ports before spawning.""" dd = data_dir out = dd / 'stdout.txt' err_sl = dd / 'stderr.txt' err = dd / Path(f'stderr.{launch_time}.txt') pidf = dd / Path(f'{Utils.SysServerName}.pid') + ports = Node._listenerPortsFromCmd(cmd) # make sure unique file name to avoid overwrite of existing log file i = 0 @@ -511,6 +551,14 @@ def launchCmd(self, cmd: List[str], data_dir: Path, launch_time: str): i = i + 1 err = dd / Path(f'stderr.{launch_time}-{i}.txt') + if waitForPorts and ports: + def areNodePortsAvailable(): + """Wait until this node's concrete listener ports can be bound.""" + return Utils.arePortsAvailable(ports) + + if not Utils.waitForBool(areNodePortsAvailable, Utils.systemWaitTimeout, sleepTime=2): + Utils.errorExit("Failed to find free node listener ports: %s" % sorted(ports)) + Utils.Print(f'spawning child: {" ".join(cmd)}') dd.mkdir(parents=True, exist_ok=True) with out.open('w') as sout, err.open('w') as serr: diff --git a/tests/TestHarness/TestHelper.py b/tests/TestHarness/TestHelper.py index e1b1b4beee..8a3c2c778b 100644 --- a/tests/TestHarness/TestHelper.py +++ b/tests/TestHarness/TestHelper.py @@ -37,8 +37,8 @@ def add_bool(self, flag, help, action='store_true'): # pylint: disable=too-many-instance-attributes class TestHelper(object): LOCAL_HOST="localhost" - DEFAULT_PORT=8888 - DEFAULT_WALLET_PORT=9899 + DEFAULT_PORT=Utils.shardPort(8888) + DEFAULT_WALLET_PORT=Utils.shardPort(9899) @staticmethod # pylint: disable=too-many-branches diff --git a/tests/TestHarness/TransactionGeneratorsLauncher.py b/tests/TestHarness/TransactionGeneratorsLauncher.py index ef7b713199..8b282d5108 100644 --- a/tests/TestHarness/TransactionGeneratorsLauncher.py +++ b/tests/TestHarness/TransactionGeneratorsLauncher.py @@ -113,7 +113,9 @@ def parseArgs(): parser.add_argument("abi_file", type=str, help="The path to the contract abi file to use for the supplied transaction action data") parser.add_argument("actions_data", type=str, help="The json actions data file or json actions data description string to use") parser.add_argument("actions_auths", type=str, help="The json actions auth file or json actions auths description string to use, containting authAcctName to activePrivateKey pairs.") - parser.add_argument("connection_pair_list", type=str, help="Comma separated list of endpoint:port combinations to send transactions to", default="localhost:9876") + parser.add_argument("connection_pair_list", type=str, + help="Comma separated list of endpoint:port combinations to send transactions to", + default=f"localhost:{Utils.shardPort(9876)}") parser.add_argument("endpoint_mode", type=str, help="Endpoint mode (\"p2p\", \"http\"). \ In \"p2p\" mode transactions will be directed to the p2p endpoint on a producer node. \ In \"http\" mode transactions will be directed to the http endpoint on an api node.", diff --git a/tests/TestHarness/WalletMgr.py b/tests/TestHarness/WalletMgr.py index aaa3e768dc..e16f38ce56 100644 --- a/tests/TestHarness/WalletMgr.py +++ b/tests/TestHarness/WalletMgr.py @@ -20,12 +20,14 @@ class WalletMgr(object): # pylint: disable=too-many-arguments # walletd [True|False] True=Launch wallet(kiod) process; False=Manage launch process externally. - def __init__(self, walletd, nodeopPort=8888, nodeopHost="localhost", port=9899, host="localhost", keepRunning=False, keepLogs=False): + def __init__(self, walletd, nodeopPort=None, nodeopHost="localhost", port=None, host="localhost", keepRunning=False, keepLogs=False): + """Create a wallet manager using sharded default ports when callers omit explicit ports.""" atexit.register(self.shutdown) self.walletd=walletd - self.nodeopPort=nodeopPort + self.nodeopPort=Utils.shardPort(8888) if nodeopPort is None else nodeopPort self.nodeopHost=nodeopHost - self.port=port + self.port=Utils.shardPort(9899) if port is None else port + self.usesDefaultWalletPort=port is None self.host=host self.keepRunning=keepRunning self.keepLogs=keepLogs or keepRunning @@ -50,9 +52,13 @@ def isLocal(self): return self.host=="localhost" or self.host=="127.0.0.1" def findAvailablePort(self): + """Find an available wallet port within this test's shard.""" + shardLimit=Utils.shardPort(WalletMgr.__MaxPort) if self.usesDefaultWalletPort else 65535 for i in range(WalletMgr.__MaxPort): port=self.port+i # pyright: ignore[reportOptionalOperand] - if port > WalletMgr.__MaxPort: + if Utils.getTestPortOffset() > 0 and port > shardLimit: + break + if Utils.getTestPortOffset() == 0 and port > WalletMgr.__MaxPort: port-=WalletMgr.__MaxPort if Utils.arePortsAvailable(port): return port diff --git a/tests/TestHarness/launcher.py b/tests/TestHarness/launcher.py index 821abdab9c..a144fe677f 100644 --- a/tests/TestHarness/launcher.py +++ b/tests/TestHarness/launcher.py @@ -50,8 +50,8 @@ class nodeDefinition: data_dir_name: str = field(init=False) p2p_port: int = 0 http_port: int = 0 - base_p2p_port: ClassVar[int] = 9876 - base_http_port: ClassVar[int] = 8888 + base_p2p_port: ClassVar[int] = Utils.shardPort(9876) + base_http_port: ClassVar[int] = Utils.shardPort(8888) host_name: str = 'localhost' public_name: str = 'localhost' listen_addr: str = '0.0.0.0' diff --git a/tests/TestHarness/testUtils.py b/tests/TestHarness/testUtils.py index 3209fdaba2..d060bc4501 100644 --- a/tests/TestHarness/testUtils.py +++ b/tests/TestHarness/testUtils.py @@ -90,9 +90,68 @@ class Utils: ConfigDir=f"{DataPath}/" TimeFmt='%Y-%m-%dT%H:%M:%S.%f' + TestPortOffsetEnvVar="SYSIO_TEST_PORT_OFFSET" + _testPortOffset=None # lock to serialize writes to subprocess_results.log across threads _check_output_lock = threading.Lock() + @staticmethod + def getTestPortOffset(): + """Return the configured per-test port shard offset.""" + if Utils._testPortOffset is not None: + return Utils._testPortOffset + + rawOffset=os.environ.get(Utils.TestPortOffsetEnvVar, "0") + try: + offset=int(rawOffset) + except ValueError as ex: + raise RuntimeError(f"{Utils.TestPortOffsetEnvVar} must be an integer, got '{rawOffset}'") from ex + + if offset < 0: + raise RuntimeError(f"{Utils.TestPortOffsetEnvVar} must be non-negative, got {offset}") + + Utils._testPortOffset=offset + return offset + + @staticmethod + def shardPort(port): + """Apply the configured test port offset to a raw base port. + + Known local test ports are compacted into a single 256-port shard so + adjacent CTest port shards cannot overlap HTTP, P2P, SHiP, or wallet + listeners from different tests. + """ + assert(isinstance(port, int)) + offset=Utils.getTestPortOffset() + if offset == 0: + return port + + shardBase=8888 + offset + compactPortMap={ + 8788: shardBase + 100, + 7899: shardBase + 150, + 8080: shardBase + 151, + 9011: shardBase + 152, + 8888: shardBase + 200, + } + + if port in compactPortMap: + shiftedPort=compactPortMap[port] + elif 9776 <= port < 9823: + shiftedPort=shardBase + 153 + (port - 9776) + elif 9876 <= port < 9899: + shiftedPort=shardBase + 225 + (port - 9876) + elif 9899 <= port <= 9999: + shiftedPort=shardBase + min(port - 9899, 99) + else: + shiftedPort=port + offset + + if shiftedPort < 1 or shiftedPort > 65535: + raise RuntimeError( + f"Port {port} shifted by {Utils.TestPortOffsetEnvVar}={Utils.getTestPortOffset()} " + f"produces invalid port {shiftedPort}") + return shiftedPort + @staticmethod def timestamp(): return datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%S.%f") @@ -371,7 +430,7 @@ def processSysioUtilCmd(cmd, cmdDesc, silentErrors=True, exitOnError=False, exit @staticmethod def arePortsAvailable(ports): - """Check if specified port (as int) or ports (as set) is/are available for listening on.""" + """Check whether final, already-sharded ports are available for listening on.""" assert(ports) if isinstance(ports, int): ports={ports} diff --git a/tests/auto_bp_gossip_peering_test.py b/tests/auto_bp_gossip_peering_test.py index 465d6d7d95..11e2690694 100755 --- a/tests/auto_bp_gossip_peering_test.py +++ b/tests/auto_bp_gossip_peering_test.py @@ -46,23 +46,21 @@ cluster.setWalletMgr(walletMgr) def getHostName(nodeId): - port = cluster.p2pBasePort + nodeId if producer_name == 'defproducerf': hostname = 'ext-ip0:9999' else: - hostname = "localhost:" + str(port) + hostname = cluster.getNodeP2pEndpoint(nodeId) return hostname peer_names = {} for nodeId in range(0, producerNodes): producer_name = "defproducer" + chr(ord('a') + nodeId) - port = cluster.p2pBasePort + nodeId hostname = getHostName(nodeId) peer_names[hostname] = producer_name -auto_bp_peer_arg = f" --p2p-auto-bp-peer defproducera,localhost:{cluster.p2pBasePort}" +auto_bp_peer_arg = f" --p2p-auto-bp-peer defproducera,{cluster.getNodeP2pEndpoint(0)}" -peer_names["localhost:9776"] = "bios" +peer_names[cluster.getBiosP2pEndpoint()] = "bios" testSuccessful = False try: @@ -218,7 +216,7 @@ def verifyGossipConnections(scheduled_producers): "Timed out waiting for all gossip connections to be established" Print("Manual connect node_19 defproducert to node_04 defproducere") - cluster.nodes[19].processUrllibRequest("net", "connect", payload="localhost:9880", exitOnError=True) + cluster.nodes[19].processUrllibRequest("net", "connect", payload=cluster.getNodeP2pEndpoint(4), exitOnError=True) Print("Set new producers b,h,m,r") assert cluster.setProds(["defproducerb", "defproducerh", "defproducerm", "defproducerr"]), "setprods failed" diff --git a/tests/auto_bp_peering_test.py b/tests/auto_bp_peering_test.py index 04daf3c631..b62a18e516 100755 --- a/tests/auto_bp_peering_test.py +++ b/tests/auto_bp_peering_test.py @@ -41,11 +41,10 @@ cluster.setWalletMgr(walletMgr) def getHostName(nodeId): - port = cluster.p2pBasePort + nodeId if producer_name == 'defproducerf': hostname = 'ext-ip0:9999' else: - hostname = "localhost:" + str(port) + hostname = cluster.getNodeP2pEndpoint(nodeId) return hostname peer_names = {} @@ -53,13 +52,12 @@ def getHostName(nodeId): auto_bp_peer_args = "" for nodeId in range(0, producerNodes): producer_name = "defproducer" + chr(ord('a') + nodeId) - port = cluster.p2pBasePort + nodeId hostname = getHostName(nodeId) peer_names[hostname] = producer_name auto_bp_peer_args += (" --p2p-auto-bp-peer " + producer_name + "," + hostname) -peer_names["localhost:9776"] = "bios" +peer_names[cluster.getBiosP2pEndpoint()] = "bios" testSuccessful = False try: diff --git a/tests/cli_test.py b/tests/cli_test.py index bee578999b..da9dc45baf 100755 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -14,7 +14,7 @@ import signal from pathlib import Path -from TestHarness import Account, Node, ReturnType, Utils, WalletMgr +from TestHarness import Account, Node, ReturnType, TestHelper, Utils, WalletMgr testSuccessful=False @@ -322,8 +322,12 @@ def clio_abi_file_test(): assert(b'"memo": "hello"' in outs) def abi_file_with_nodeop_test(): + """Verify --abi-file is honored when clio submits actions through nodeop.""" # push action token transfer with option `--abi-file` global testSuccessful + malicious_token_abi_path = None + node = None + walletMgr = None try: biosDir = os.path.abspath(os.getcwd() + "/libraries/testing/contracts/sysio.bios") contractDir = os.path.abspath(os.getcwd() + "/contracts/sysio.token") @@ -342,8 +346,9 @@ def abi_file_with_nodeop_test(): f.truncate() tries = 30 - while not Utils.arePortsAvailable(set(range(8888, 8889))): - Utils.Print("ERROR: Another process is listening on nodeop test port 8888. wait...") + nodeopPort = TestHelper.DEFAULT_PORT + while not Utils.arePortsAvailable(set(range(nodeopPort, nodeopPort + 1))): + Utils.Print(f"ERROR: Another process is listening on nodeop test port {nodeopPort}. wait...") if tries == 0: assert False tries -= 1 @@ -353,8 +358,8 @@ def abi_file_with_nodeop_test(): os.makedirs(data_dir, exist_ok=True) walletMgr = WalletMgr(True) walletMgr.launch() - cmd = "./programs/nodeop/nodeop -e -p sysio --signature-provider wire-1,wire,wire,SYS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV,KEY:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 --plugin sysio::trace_api_plugin --trace-no-abis --plugin sysio::producer_plugin --plugin sysio::producer_api_plugin --plugin sysio::chain_api_plugin --plugin sysio::chain_plugin --plugin sysio::http_plugin --access-control-allow-origin=* --http-validate-host=false --max-transaction-time=-1 --resource-monitor-not-shutdown-on-threshold-exceeded " + "--data-dir " + data_dir + " --config-dir " + data_dir - node = Node('localhost', 8888, nodeId, data_dir=Path(data_dir), config_dir=Path(data_dir), cmd=shlex.split(cmd), launch_time=datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S'), walletMgr=walletMgr) + cmd = f"./programs/nodeop/nodeop -e -p sysio --signature-provider wire-1,wire,wire,SYS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV,KEY:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 --plugin sysio::trace_api_plugin --trace-no-abis --plugin sysio::producer_plugin --plugin sysio::producer_api_plugin --plugin sysio::chain_api_plugin --plugin sysio::chain_plugin --plugin sysio::http_plugin --access-control-allow-origin=* --http-validate-host=false --http-server-address=localhost:{nodeopPort} --max-transaction-time=-1 --resource-monitor-not-shutdown-on-threshold-exceeded " + "--data-dir " + data_dir + " --config-dir " + data_dir + node = Node('localhost', nodeopPort, nodeId, data_dir=Path(data_dir), config_dir=Path(data_dir), cmd=shlex.split(cmd), launch_time=datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S'), walletMgr=walletMgr) if not node or not Utils.waitForBool(node.checkPulse, timeout=15): Utils.Print("ERROR: node doesn't appear to be running...") assert False, "node doesn't appear to be running" @@ -424,7 +429,8 @@ def abi_file_with_nodeop_test(): if os.path.exists(malicious_token_abi_path): os.remove(malicious_token_abi_path) - walletMgr.testFailed = not testSuccessful + if walletMgr: + walletMgr.testFailed = not testSuccessful def clio_protobuf_abi_test(): """Test that clio can pack/unpack protobuf action data using --abi-file""" diff --git a/tests/disaster_recovery_2.py b/tests/disaster_recovery_2.py index 87321494b9..f09bcd1879 100755 --- a/tests/disaster_recovery_2.py +++ b/tests/disaster_recovery_2.py @@ -101,7 +101,7 @@ node0.waitForHeadToAdvance(timeout=3) # should time out Print("Disconnect the producing node (Node0) from peer Node1") - node0.processUrllibRequest("net", "disconnect", "localhost:9877") + node0.processUrllibRequest("net", "disconnect", cluster.getNodeP2pEndpoint(1)) assert not node0.waitForLibToAdvance(timeout=10), "Node0 LIB still advancing after disconnect" assert not node1.waitForHeadToAdvance(timeout=5), "Node1 head still advancing after disconnect" diff --git a/tests/http_plugin_test.py b/tests/http_plugin_test.py index 9be03b64bf..5c649becdd 100755 --- a/tests/http_plugin_test.py +++ b/tests/http_plugin_test.py @@ -33,7 +33,7 @@ TestHelper.printSystemInfo("BEGIN") Print("Stand up cluster") - node0_extra_config = "--http-validate-host true --http-server-address 127.0.0.1:8888" + node0_extra_config = f"--http-validate-host true --http-server-address 127.0.0.1:{cluster.port}" if cluster.launch(dontBootstrap=True, loadSystemContract=False, specificExtraNodeopArgs = {0: node0_extra_config}) is False: cmdError("launcher") errorExit("Failed to stand up sys cluster.") @@ -41,8 +41,8 @@ cluster.getInfos() node0 = cluster.nodes[0] - ## HTTP plugin listens to 127.0.0.1:8888 by default. With the --http-validate-host=true, - ## the HTTP request to "http://localhost:8888" should fail because the HOST header doesn't + ## HTTP plugin listens to 127.0.0.1: by default. With the --http-validate-host=true, + ## the HTTP request to "http://localhost:" should fail because the HOST header doesn't ## match "127.0.0.1". def get_info_status(url): @@ -64,4 +64,3 @@ def get_info_status(url): exitCode = 0 if testSuccessful else 1 exit(exitCode) - diff --git a/tests/lib_advance_test.py b/tests/lib_advance_test.py index 03b1c8b163..8f84030fb1 100755 --- a/tests/lib_advance_test.py +++ b/tests/lib_advance_test.py @@ -123,7 +123,7 @@ Print("Relaunching the non-producing bridge node to connect the nodes") if not nonProdNode.relaunch(): - errorExit(f"Failure - (non-production) node {nonProdNode.nodeNum} should have restarted") + errorExit(f"Failure - (non-production) node {nonProdNode.nodeId} should have restarted") while prodD.getInfo()['last_irreversible_block_num'] < transBlockNum: Print("Wait for LIB to move, which indicates prodD may have forked out the branch") diff --git a/tests/nodeop_contrl_c_test.py b/tests/nodeop_contrl_c_test.py index 8153a326cf..2d25976ea8 100755 --- a/tests/nodeop_contrl_c_test.py +++ b/tests/nodeop_contrl_c_test.py @@ -29,8 +29,8 @@ activateIF=args.activate_if walletPort=args.wallet_port walletMgr=WalletMgr(True, port=walletPort) -producerEndpoint = '127.0.0.1:8888' -httpServerAddress = '127.0.0.1:8889' +producerEndpoint = f'127.0.0.1:{cluster.port}' +httpServerAddress = f'127.0.0.1:{cluster.port + 1}' testSuccessful=False trxGenLauncher=None diff --git a/tests/nodeop_forked_chain_test.py b/tests/nodeop_forked_chain_test.py index 7adb3976f1..c2a3fe195f 100755 --- a/tests/nodeop_forked_chain_test.py +++ b/tests/nodeop_forked_chain_test.py @@ -155,7 +155,10 @@ def getMinHeadAndLib(prodNodes): Print("Stand up cluster") specificExtraNodeopArgs={} shipNodeNum = 0 - specificExtraNodeopArgs[shipNodeNum]="--plugin sysio::state_history_plugin" + specificExtraNodeopArgs[shipNodeNum]=( + "--plugin sysio::state_history_plugin " + f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)}" + ) # producer nodes will be mapped to 0 through totalProducerNodes-1, so the number totalProducerNodes will be the non-producing node specificExtraNodeopArgs[totalProducerNodes]="--plugin sysio::test_control_api_plugin" @@ -285,7 +288,11 @@ def getBlock(self, blockNum): end_block_num = start_block_num + block_range shipClient = "tests/ship_streamer" - cmd = f"{shipClient} --start-block-num {start_block_num} --end-block-num {end_block_num} --fetch-block --fetch-traces --fetch-deltas" + cmd = ( + f"{shipClient} --socket-address 127.0.0.1:{Utils.shardPort(8080)} " + f"--start-block-num {start_block_num} --end-block-num {end_block_num} " + "--fetch-block --fetch-traces --fetch-deltas" + ) if Utils.Debug: Utils.Print(f"cmd: {cmd}") clients = [] files = [] diff --git a/tests/nodeop_late_block_test_shape.json b/tests/nodeop_late_block_test_shape.json index 707d5f9bfb..95a1f8f554 100644 --- a/tests/nodeop_late_block_test_shape.json +++ b/tests/nodeop_late_block_test_shape.json @@ -56,7 +56,7 @@ ], "peers": [ "testnet_00", - "testnet_01" + "testnet_02" ], "producers": [ "defproducerd", diff --git a/tests/p2p_multiple_listen_test.py b/tests/p2p_multiple_listen_test.py index 9a58435483..58040c2171 100755 --- a/tests/p2p_multiple_listen_test.py +++ b/tests/p2p_multiple_listen_test.py @@ -38,10 +38,14 @@ Print(f'producing nodes: {pnodes}, delay between nodes launch: {delay} second{"s" if delay != 1 else ""}') Print("Stand up cluster") + alternateListenEndpoint = f"0.0.0.0:{Utils.shardPort(9779)}" + alternatePeerEndpoint = f"localhost:{Utils.shardPort(9779)}" specificArgs = { - '0': '--agent-name node-00 --p2p-listen-endpoint 0.0.0.0:9876 --p2p-listen-endpoint 0.0.0.0:9779 --p2p-server-address ext-ip0:20000 --p2p-server-address ext-ip1:20001 --plugin sysio::net_api_plugin', - '2': '--agent-name node-02 --p2p-peer-address localhost:9779 --plugin sysio::net_api_plugin', - '4': '--agent-name node-04 --p2p-peer-address localhost:9876 --plugin sysio::net_api_plugin', + '0': f'--agent-name node-00 --p2p-listen-endpoint 0.0.0.0:{cluster.getNodeP2pPort(0)} ' + f'--p2p-listen-endpoint {alternateListenEndpoint} --p2p-server-address ext-ip0:20000 ' + f'--p2p-server-address ext-ip1:20001 --plugin sysio::net_api_plugin', + '2': f'--agent-name node-02 --p2p-peer-address {alternatePeerEndpoint} --plugin sysio::net_api_plugin', + '4': f'--agent-name node-04 --p2p-peer-address {cluster.getNodeP2pEndpoint(0)} --plugin sysio::net_api_plugin', } if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo='line', delay=delay, activateIF=activateIF, specificExtraNodeopArgs=specificArgs) is False: @@ -73,9 +77,11 @@ if conn['is_socket_open']: open_socket_count += 1 if conn['last_handshake']['agent'] == 'node-02': - assert conn['last_handshake']['p2p_address'].split()[0] == 'localhost:9878', f"Connected node is listening on '{conn['last_handshake']['p2p_address'].split()[0]}' instead of port 9878" + expectedEndpoint = cluster.getNodeP2pEndpoint(2) + assert conn['last_handshake']['p2p_address'].split()[0] == expectedEndpoint, f"Connected node is listening on '{conn['last_handshake']['p2p_address'].split()[0]}' instead of {expectedEndpoint}" elif conn['last_handshake']['agent'] == 'node-04': - assert conn['last_handshake']['p2p_address'].split()[0] == 'localhost:9880', f"Connected node is listening on '{conn['last_handshake']['p2p_address'].split()[0]}' instead of port 9880" + expectedEndpoint = cluster.getNodeP2pEndpoint(4) + assert conn['last_handshake']['p2p_address'].split()[0] == expectedEndpoint, f"Connected node is listening on '{conn['last_handshake']['p2p_address'].split()[0]}' instead of {expectedEndpoint}" assert open_socket_count == 2, 'Node 0 is expected to have exactly two open sockets' connections = cluster.nodes[2].processUrllibRequest('net', 'connections') diff --git a/tests/p2p_no_blocks_test.py b/tests/p2p_no_blocks_test.py index 8b0376fa8e..99ece671ef 100755 --- a/tests/p2p_no_blocks_test.py +++ b/tests/p2p_no_blocks_test.py @@ -56,15 +56,17 @@ # 02 & 03 are connected to the bios node to get blocks until bios node is killed. # specificExtraNodeopArgs = {} + trxOnlyPort02 = Utils.shardPort(9902) + trxOnlyPort03 = Utils.shardPort(9903) # nonProdNode01 will connect normally but will not send blocks because 02 & 03 have specified :trx only - specificExtraNodeopArgs[1] = f'--p2p-peer-address localhost:9902 --p2p-peer-address localhost:9903 ' + specificExtraNodeopArgs[1] = f'--p2p-peer-address localhost:{trxOnlyPort02} --p2p-peer-address localhost:{trxOnlyPort03} ' # add a trx only listen endpoint to noBlocks02 & noBlocks03 - specificExtraNodeopArgs[2] = f'--p2p-peer-address localhost:9776 --read-mode speculative ' # connect to bios - specificExtraNodeopArgs[2] += f'--p2p-listen-endpoint localhost:9878 --p2p-server-address localhost:9878 ' - specificExtraNodeopArgs[2] += f'--p2p-listen-endpoint localhost:9902:trx --p2p-server-address localhost:9902:trx ' - specificExtraNodeopArgs[3] = f'--p2p-peer-address localhost:9776 ' # connect to bios - specificExtraNodeopArgs[3] += f'--p2p-listen-endpoint localhost:9879 --p2p-server-address localhost:9879 ' - specificExtraNodeopArgs[3] += f'--p2p-listen-endpoint localhost:9903:trx --p2p-server-address localhost:9903:trx ' + specificExtraNodeopArgs[2] = f'--p2p-peer-address {cluster.getBiosP2pEndpoint()} --read-mode speculative ' # connect to bios + specificExtraNodeopArgs[2] += f'--p2p-listen-endpoint {cluster.getNodeP2pEndpoint(2)} --p2p-server-address {cluster.getNodeP2pEndpoint(2)} ' + specificExtraNodeopArgs[2] += f'--p2p-listen-endpoint localhost:{trxOnlyPort02}:trx --p2p-server-address localhost:{trxOnlyPort02}:trx ' + specificExtraNodeopArgs[3] = f'--p2p-peer-address {cluster.getBiosP2pEndpoint()} ' # connect to bios + specificExtraNodeopArgs[3] += f'--p2p-listen-endpoint {cluster.getNodeP2pEndpoint(3)} --p2p-server-address {cluster.getNodeP2pEndpoint(3)} ' + specificExtraNodeopArgs[3] += f'--p2p-listen-endpoint localhost:{trxOnlyPort03}:trx --p2p-server-address localhost:{trxOnlyPort03}:trx ' if cluster.launch(pnodes=pnodes, unstartedNodes=2, totalNodes=total_nodes, prodCount=prod_count, extraNodeopArgs="--connection-cleanup-period 3", specificExtraNodeopArgs=specificExtraNodeopArgs, topo='./tests/p2p_no_blocks_test_shape.json', delay=delay, activateIF=activateIF, biosFinalizer=False) is False: diff --git a/tests/p2p_no_listen_test.py b/tests/p2p_no_listen_test.py index 065ec3f035..ad200aaf9b 100755 --- a/tests/p2p_no_listen_test.py +++ b/tests/p2p_no_listen_test.py @@ -42,9 +42,9 @@ '--data-dir', Utils.DataDir, '--http-server-address', - 'localhost:8888' + f'localhost:{TestHelper.DEFAULT_PORT}' ] - node = Node('localhost', '8888', '00', data_dir=pathlib.Path(Utils.DataDir), + node = Node('localhost', TestHelper.DEFAULT_PORT, '00', data_dir=pathlib.Path(Utils.DataDir), config_dir=pathlib.Path(Utils.ConfigDir), cmd=cmd) time.sleep(1) @@ -54,8 +54,9 @@ node.waitForBlock(5) s = socket.socket() - err = s.connect_ex(('localhost',9876)) - assert err == errno.ECONNREFUSED, 'Connection to port 9876 must be refused' + p2pPort = Utils.shardPort(9876) + err = s.connect_ex(('localhost', p2pPort)) + assert err == errno.ECONNREFUSED, f'Connection to port {p2pPort} must be refused' testSuccessful=True finally: diff --git a/tests/p2p_peer_auth_test.py b/tests/p2p_peer_auth_test.py index b78d9e187b..948f8954d2 100755 --- a/tests/p2p_peer_auth_test.py +++ b/tests/p2p_peer_auth_test.py @@ -66,13 +66,14 @@ def peer_private_key_json(pub, pvt): # Configure unstarted nodes with auth args baked into their start commands. # JSON values must survive shlex.split in the launcher — protect with single quotes. # Node1 (authorized): trusts node0's key, provides its own identity + node0P2pEndpoint = cluster.getNodeP2pEndpoint(0) specificExtraNodeopArgs = {} specificExtraNodeopArgs[0] = "--plugin sysio::net_api_plugin" specificExtraNodeopArgs[1] = ( f"--allowed-connection specified" f" --peer-key '{peer_key_json(KEY0_PUB)}'" f" --peer-private-key '{peer_private_key_json(KEY1_PUB, KEY1_PVT)}'" - f" --p2p-peer-address localhost:9876" + f" --p2p-peer-address {node0P2pEndpoint}" ) # Node2 (unauthorized): authenticates itself with KEY2 (not trusted by node0). # Must trust node0's KEY0 so it doesn't preemptively reject node0 before @@ -81,7 +82,7 @@ def peer_private_key_json(pub, pvt): f"--allowed-connection specified" f" --peer-key '{peer_key_json(KEY0_PUB)}'" f" --peer-private-key '{peer_private_key_json(KEY2_PUB, KEY2_PVT)}'" - f" --p2p-peer-address localhost:9876" + f" --p2p-peer-address {node0P2pEndpoint}" ) Print("Stand up cluster: 1 producer, 2 unstarted nodes") diff --git a/tests/plugin_http_api_test.py b/tests/plugin_http_api_test.py index 094a55d47c..7bec666b6a 100755 --- a/tests/plugin_http_api_test.py +++ b/tests/plugin_http_api_test.py @@ -53,6 +53,7 @@ class PluginHttpTest(unittest.TestCase): config_dir = Path(Utils.getNodeConfigDir(node_id)) empty_content_dict = {} http_post_invalid_param = '{invalid}' + p2p_peer_endpoint = f"localhost:{Utils.shardPort(9011)}" SYSIO_ACCT_PRIVATE_DEFAULT_KEY = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" SYSIO_ACCT_PUBLIC_DEFAULT_KEY = "SYS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" @@ -88,6 +89,9 @@ def startEnv(self) : self.createDataDir(self) self.createConfigDir(self) self.kiod.launch() + p2pEndpoint = f"{TestHelper.LOCAL_HOST}:{Utils.shardPort(9876)}" + httpServerAddressArg = "" if category_config.ports else ( + f"--http-server-address {TestHelper.LOCAL_HOST}:{TestHelper.DEFAULT_PORT} ") plugin_names = ["trace_api_plugin", "test_control_api_plugin", "test_control_plugin", "net_plugin", "net_api_plugin", "producer_plugin", "producer_api_plugin", "chain_api_plugin", "http_plugin", "db_size_api_plugin", "prometheus_plugin"] @@ -95,7 +99,10 @@ def startEnv(self) : nodeop_flags = (" --data-dir=%s --config-dir=%s --trace-dir=%s --trace-no-abis --access-control-allow-origin=%s " "--signature-provider wire-1,wire,wire,SYS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV,KEY:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 " "--contracts-console --http-validate-host=%s --verbose-http-errors --max-transaction-time -1 --abi-serializer-max-time-ms 30000 --http-max-response-time-ms 30000 " - "--p2p-peer-address localhost:9011 --resource-monitor-not-shutdown-on-threshold-exceeded ") % (self.data_dir, self.config_dir, self.data_dir, "\'*\'", "false") + "%s--p2p-listen-endpoint 0.0.0.0:%d --p2p-server-address %s " + "--p2p-peer-address %s --resource-monitor-not-shutdown-on-threshold-exceeded ") % ( + self.data_dir, self.config_dir, self.data_dir, "\'*\'", "false", + httpServerAddressArg, Utils.shardPort(9876), p2pEndpoint, self.p2p_peer_endpoint) nodeop_flags += category_config.nodeopArgs() start_nodeop_cmd = ("%s -e -p sysio %s %s ") % (Utils.SysServerPath, nodeop_plugins, nodeop_flags) @@ -800,7 +807,7 @@ def test_NetApi(self) : ret_json = self.nodeop.processUrllibRequest(resource, command, payload, endpoint=endpoint) self.assertEqual(ret_json["code"], 201) self.assertEqual(ret_json["payload"], 'invalid peer address') - payload = "localhost:9877" + payload = f"localhost:{Utils.shardPort(9877)}" ret_str = self.nodeop.processUrllibRequest(resource, command, payload, returnType=ReturnType.raw, endpoint=endpoint).decode('ascii') self.assertEqual("\"added connection\"", ret_str) @@ -836,10 +843,10 @@ def test_NetApi(self) : # connections with empty parameter command = "connections" ret_str = self.nodeop.processUrllibRequest(resource, command, returnType=ReturnType.raw, endpoint=endpoint).decode('ascii') - self.assertIn("\"peer\":\"localhost:9011\"", ret_str) + self.assertIn(f"\"peer\":\"{self.p2p_peer_endpoint}\"", ret_str) # connections with empty content parameter ret_str = self.nodeop.processUrllibRequest(resource, command, self.empty_content_dict, returnType=ReturnType.raw, endpoint=endpoint).decode('ascii') - self.assertIn("\"peer\":\"localhost:9011\"", ret_str) + self.assertIn(f"\"peer\":\"{self.p2p_peer_endpoint}\"", ret_str) # connections with invalid parameter ret_json = self.nodeop.processUrllibRequest(resource, command, self.http_post_invalid_param, endpoint=endpoint) self.assertEqual(ret_json["code"], 400) diff --git a/tests/resource_monitor_plugin_test.py b/tests/resource_monitor_plugin_test.py index ff641b3a0c..affdb06581 100755 --- a/tests/resource_monitor_plugin_test.py +++ b/tests/resource_monitor_plugin_test.py @@ -178,9 +178,9 @@ def testAll(): testCommon("Resmon not enabled: no arguments", "", ["interval set to 2", "threshold set to 90", "Shutdown flag when threshold exceeded set to true", "snapshots's file system to be monitored", "blocks's file system to be monitored", "state's file system to be monitored"]) # default arguments with registered directories - testCommon("Resmon not enabled: Producer, Chain, State History and Trace Api", "--plugin sysio::state_history_plugin --state-history-dir=/tmp/state-history --disable-replay-opts --plugin sysio::trace_api_plugin --trace-dir=/tmp/trace --trace-no-abis", ["interval set to 2", "threshold set to 90", "Shutdown flag when threshold exceeded set to true", "snapshots's file system to be monitored", "blocks's file system to be monitored", "state's file system to be monitored", "state-history's file system to be monitored", "trace's file system to be monitored"]) + testCommon("Resmon not enabled: Producer, Chain, State History and Trace Api", f"--plugin sysio::state_history_plugin --state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} --state-history-dir=/tmp/state-history --disable-replay-opts --plugin sysio::trace_api_plugin --trace-dir=/tmp/trace --trace-no-abis", ["interval set to 2", "threshold set to 90", "Shutdown flag when threshold exceeded set to true", "snapshots's file system to be monitored", "blocks's file system to be monitored", "state's file system to be monitored", "state-history's file system to be monitored", "trace's file system to be monitored"]) - testCommon("Resmon enabled: Producer, Chain, State History and Trace Api", "--plugin sysio::resource_monitor_plugin --plugin sysio::state_history_plugin --state-history-dir=/tmp/state-history --disable-replay-opts --plugin sysio::trace_api_plugin --trace-dir=/tmp/trace --trace-no-abis --resource-monitor-space-threshold=80 --resource-monitor-interval-seconds=3", ["snapshots's file system to be monitored", "blocks's file system to be monitored", "state's file system to be monitored", "state-history's file system to be monitored", "trace's file system to be monitored", "threshold set to 80", "interval set to 3", "Shutdown flag when threshold exceeded set to true"]) + testCommon("Resmon enabled: Producer, Chain, State History and Trace Api", f"--plugin sysio::resource_monitor_plugin --plugin sysio::state_history_plugin --state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} --state-history-dir=/tmp/state-history --disable-replay-opts --plugin sysio::trace_api_plugin --trace-dir=/tmp/trace --trace-no-abis --resource-monitor-space-threshold=80 --resource-monitor-interval-seconds=3", ["snapshots's file system to be monitored", "blocks's file system to be monitored", "state's file system to be monitored", "state-history's file system to be monitored", "trace's file system to be monitored", "threshold set to 80", "interval set to 3", "Shutdown flag when threshold exceeded set to true"]) # Only test minimum warning threshold (i.e. 6) to trigger warning as much as possible testInterval("Resmon enabled: set warning interval", diff --git a/tests/ship_client.cpp b/tests/ship_client.cpp index c8c62dd57c..cdad01e015 100644 --- a/tests/ship_client.cpp +++ b/tests/ship_client.cpp @@ -11,6 +11,8 @@ #include #include +#include "test_port_shard.hpp" + using mvo = fc::mutable_variant_object; using tcp = boost::asio::ip::tcp; using unixs = boost::asio::local::stream_protocol; @@ -31,7 +33,7 @@ int main(int argc, char* argv[]) { bpo::options_description cli("ship_client command line options"); bool help = false; - std::string socket_address = "127.0.0.1:8080"; + std::string socket_address = sysio::testing::default_state_history_endpoint(); uint32_t num_requests = 1; cli.add_options() diff --git a/tests/ship_kill_client_test.py b/tests/ship_kill_client_test.py index 73b5e374d5..d08b65c605 100755 --- a/tests/ship_kill_client_test.py +++ b/tests/ship_kill_client_test.py @@ -49,7 +49,13 @@ shipNodeNum = 2 specificExtraNodeopArgs={} - specificExtraNodeopArgs[shipNodeNum]="--plugin sysio::state_history_plugin --trace-history --chain-state-history --finality-data-history --state-history-stride 200 --plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin " + specificExtraNodeopArgs[shipNodeNum]=( + "--plugin sysio::state_history_plugin " + "--trace-history --chain-state-history --finality-data-history " + "--state-history-stride 200 " + f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} " + "--plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin " + ) if cluster.launch(pnodes=totalProducerNodes, loadSystemContract=False, totalNodes=totalNodes, totalProducers=totalProducerNodes, activateIF=True, biosFinalizer=False, diff --git a/tests/ship_kv_delta_test.py b/tests/ship_kv_delta_test.py index 3b78514ab2..fd1488dc3f 100755 --- a/tests/ship_kv_delta_test.py +++ b/tests/ship_kv_delta_test.py @@ -58,8 +58,10 @@ specificExtraNodeopArgs[apiNodeNum] = ( "--transaction-retry-max-storage-size-gb 100 " ) + shipAddr = f"127.0.0.1:{Utils.shardPort(8080)}" specificExtraNodeopArgs[shipNodeNum] = ( "--plugin sysio::state_history_plugin " + f"--state-history-endpoint {shipAddr} " "--trace-history --chain-state-history " "--plugin sysio::net_api_plugin " ) @@ -150,9 +152,6 @@ outFile = os.path.join(shipTempDir, "streamer.out") errFile = os.path.join(shipTempDir, "streamer.err") - # SHiP listens on default port 8080 - shipAddr = "127.0.0.1:8080" - cmd = (f"tests/ship_streamer --socket-address {shipAddr} " f"--start-block-num {startBlockNum} --end-block-num {endBlockNum} " f"--fetch-deltas") diff --git a/tests/ship_reqs_across_svnn_test.py b/tests/ship_reqs_across_svnn_test.py index 1c23c83c7a..9dfdd8fde6 100755 --- a/tests/ship_reqs_across_svnn_test.py +++ b/tests/ship_reqs_across_svnn_test.py @@ -49,7 +49,13 @@ shipNodeNum = 1 specificExtraNodeopArgs={} - specificExtraNodeopArgs[shipNodeNum]="--plugin sysio::state_history_plugin --trace-history --chain-state-history --state-history-stride 200 --plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin --finality-data-history" + specificExtraNodeopArgs[shipNodeNum]=( + "--plugin sysio::state_history_plugin " + "--trace-history --chain-state-history " + "--state-history-stride 200 " + f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} " + "--plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin --finality-data-history" + ) if cluster.launch(topo="mesh", pnodes=totalProducerNodes, totalNodes=totalNodes, activateIF=True, @@ -71,7 +77,12 @@ # Start a SHiP client and request blocks between start_block_num and end_block_num shipClient = "tests/ship_streamer" - cmd = f"{shipClient} --start-block-num {start_block_num} --end-block-num {end_block_num} --fetch-block --fetch-traces --fetch-deltas --fetch-finality-data" + shipSocketAddress = f"127.0.0.1:{Utils.shardPort(8080)}" + cmd = ( + f"{shipClient} --socket-address {shipSocketAddress} " + f"--start-block-num {start_block_num} --end-block-num {end_block_num} " + "--fetch-block --fetch-traces --fetch-deltas --fetch-finality-data" + ) if Utils.Debug: Utils.Print(f"cmd: {cmd}") shipTempDir = os.path.join(Utils.DataDir, "ship") os.makedirs(shipTempDir, exist_ok = True) diff --git a/tests/ship_restart_test.py b/tests/ship_restart_test.py index a79c5a1f5c..b2c5cd0811 100755 --- a/tests/ship_restart_test.py +++ b/tests/ship_restart_test.py @@ -60,7 +60,13 @@ def corruptedHeaderTest(pos, corruptedValue, shipNode): specificExtraNodeopArgs={} specificExtraNodeopArgs[prodNodeId]="--plugin sysio::producer_api_plugin" - specificExtraNodeopArgs[shipNodeId]="--plugin sysio::state_history_plugin --trace-history --chain-state-history --finality-data-history --state-history-stride 200 --plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin" + specificExtraNodeopArgs[shipNodeId]=( + "--plugin sysio::state_history_plugin " + "--trace-history --chain-state-history --finality-data-history " + "--state-history-stride 200 " + f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} " + "--plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin" + ) if cluster.launch(topo="mesh", pnodes=totalProducerNodes, totalNodes=totalNodes, activateIF=True, diff --git a/tests/ship_streamer.cpp b/tests/ship_streamer.cpp index 7613c19dfb..8648b815f7 100644 --- a/tests/ship_streamer.cpp +++ b/tests/ship_streamer.cpp @@ -12,6 +12,8 @@ #include #include +#include "test_port_shard.hpp" + using mvo = fc::mutable_variant_object; namespace bpo = boost::program_options; @@ -26,7 +28,7 @@ int main(int argc, char* argv[]) { bpo::options_description cli("ship_streamer command line options"); bool help = false; - std::string socket_address = "127.0.0.1:8080"; + std::string socket_address = sysio::testing::default_state_history_endpoint(); uint32_t start_block_num = 1; uint32_t end_block_num = std::numeric_limits::max()-1; bool irreversible_only = false; diff --git a/tests/ship_streamer_test.py b/tests/ship_streamer_test.py index 7c1655cd27..176e24d512 100755 --- a/tests/ship_streamer_test.py +++ b/tests/ship_streamer_test.py @@ -65,7 +65,13 @@ shipNodeNum = 3 specificExtraNodeopArgs={} - specificExtraNodeopArgs[shipNodeNum]="--plugin sysio::state_history_plugin --trace-history --chain-state-history --state-history-stride 200 --plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin " + specificExtraNodeopArgs[shipNodeNum]=( + "--plugin sysio::state_history_plugin " + "--trace-history --chain-state-history " + "--state-history-stride 200 " + f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} " + "--plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin " + ) if args.finality_data_history: specificExtraNodeopArgs[shipNodeNum]+=" --finality-data-history" # producer nodes will be mapped to 0 through totalProducerNodes-1, so the number totalProducerNodes will be the non-producing node @@ -139,7 +145,17 @@ end_block_num = start_block_num + block_range shipClient = "tests/ship_streamer" - cmd = f"{shipClient} --start-block-num {start_block_num} --end-block-num {end_block_num} --fetch-block --fetch-traces --fetch-deltas" + shipSocketAddress = f"127.0.0.1:{Utils.shardPort(8080)}" + + def makeShipStreamerCmd(startBlockNum, endBlockNum): + """Return a ship_streamer command targeting this test's sharded SHiP endpoint.""" + return ( + f"{shipClient} --socket-address {shipSocketAddress} " + f"--start-block-num {startBlockNum} --end-block-num {endBlockNum} " + "--fetch-block --fetch-traces --fetch-deltas" + ) + + cmd = makeShipStreamerCmd(start_block_num, end_block_num) if args.finality_data_history: cmd += " --fetch-finality-data" if Utils.Debug: Utils.Print(f"cmd: {cmd}") @@ -238,7 +254,7 @@ start_block_num = afterReplayBlockNum block_range = 0 end_block_num = start_block_num + block_range - cmd = f"{shipClient} --start-block-num {start_block_num} --end-block-num {end_block_num} --fetch-block --fetch-traces --fetch-deltas" + cmd = makeShipStreamerCmd(start_block_num, end_block_num) if args.finality_data_history: cmd += " --fetch-finality-data" if Utils.Debug: Utils.Print(f"cmd: {cmd}") @@ -289,7 +305,7 @@ start_block_num = afterSnapshotBlockNum block_range = 0 end_block_num = start_block_num + block_range - cmd = f"{shipClient} --start-block-num {start_block_num} --end-block-num {end_block_num} --fetch-block --fetch-traces --fetch-deltas" + cmd = makeShipStreamerCmd(start_block_num, end_block_num) if args.finality_data_history: cmd += " --fetch-finality-data" if Utils.Debug: Utils.Print(f"cmd: {cmd}") diff --git a/tests/ship_test.py b/tests/ship_test.py index d09bdde327..a9549cadf3 100755 --- a/tests/ship_test.py +++ b/tests/ship_test.py @@ -58,7 +58,12 @@ specificExtraNodeopArgs={} # non-producing nodes are at the end of the cluster's nodes, so reserving the last one for state_history_plugin shipNodeNum = totalNodes - 1 - specificExtraNodeopArgs[shipNodeNum]="--plugin sysio::state_history_plugin --sync-fetch-span 200 --plugin sysio::net_api_plugin " + specificExtraNodeopArgs[shipNodeNum]=( + "--plugin sysio::state_history_plugin " + "--sync-fetch-span 200 " + f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} " + "--plugin sysio::net_api_plugin " + ) if args.unix_socket: specificExtraNodeopArgs[shipNodeNum] += "--state-history-unix-socket-path ship.sock" @@ -82,6 +87,8 @@ cmd = "%s --num-requests %d" % (shipClient, args.num_requests) if args.unix_socket: cmd += " -a ws+unix:///%s" % (Utils.getNodeDataDir(shipNodeNum, "ship.sock")) + else: + cmd += " -a 127.0.0.1:%d" % (Utils.shardPort(8080)) if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) clients = [] files = [] @@ -118,7 +125,7 @@ minLastBN = sys.maxsize for index in range(0, len(clients)): done = False - shipClientErrorFile = "%s%d.err" % (shipClientFilePrefix, i) + shipClientErrorFile = "%s%d.err" % (shipClientFilePrefix, index) with open(shipClientErrorFile, "r") as errFile: statuses = None lines = errFile.readlines() @@ -139,7 +146,7 @@ if statusDesc == "error": Utils.errorExit("ship_client reporting error see: %s." % (shipClientErrorFile)) - assert done, Print("ERROR: Did not find a \"done\" status for client %d" % (i)) + assert done, Print("ERROR: Did not find a \"done\" status for client %d" % (index)) Print("All clients active from block num: %s to block_num: %s." % (maxFirstBN, minLastBN)) diff --git a/tests/split_blocklog_replay_test.py b/tests/split_blocklog_replay_test.py index a3de560b8c..548531bd35 100755 --- a/tests/split_blocklog_replay_test.py +++ b/tests/split_blocklog_replay_test.py @@ -18,7 +18,7 @@ try: start_nodeop_cmd = f"{Utils.SysServerPath} -e -p sysio --data-dir={data_dir} --config-dir={config_dir} --blocks-log-stride 10" \ - " --plugin=sysio::http_plugin --plugin=sysio::chain_api_plugin --http-server-address=localhost:8888" + f" --plugin=sysio::http_plugin --plugin=sysio::chain_api_plugin --http-server-address=localhost:{TestHelper.DEFAULT_PORT}" nodeop.launchCmd(start_nodeop_cmd, node_id) time.sleep(2) diff --git a/tests/test_port_shard.hpp b/tests/test_port_shard.hpp new file mode 100644 index 0000000000..d73e291d7c --- /dev/null +++ b/tests/test_port_shard.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace sysio::testing { + +inline constexpr const char* test_port_offset_env_var = "SYSIO_TEST_PORT_OFFSET"; +inline constexpr uint16_t default_state_history_port = 8080; +inline constexpr uint32_t compact_shard_anchor_port = 8888; +inline constexpr uint32_t compact_wallet_first_port = 9899; +inline constexpr uint32_t compact_wallet_last_port = 9999; +inline constexpr uint32_t compact_alternate_first_port = 9776; +inline constexpr uint32_t compact_alternate_last_port = 9822; +inline constexpr uint32_t compact_p2p_first_port = 9876; +inline constexpr uint32_t compact_p2p_last_port = 9898; +inline constexpr uint32_t compact_wallet_slot = 0; +inline constexpr uint32_t compact_bios_http_slot = 100; +inline constexpr uint32_t compact_ship_slot = 150; +inline constexpr uint32_t compact_state_history_slot = 151; +inline constexpr uint32_t compact_alternate_service_slot = 152; +inline constexpr uint32_t compact_alternate_p2p_slot = 153; +inline constexpr uint32_t compact_http_slot = 200; +inline constexpr uint32_t compact_p2p_slot = 225; + +/** Return the current test's port shard offset from the CTest environment. */ +inline uint32_t test_port_offset() { + const char* raw_offset = std::getenv(test_port_offset_env_var); + if(raw_offset == nullptr || raw_offset[0] == '\0') + return 0; + + try { + return static_cast(std::stoul(raw_offset)); + } catch(const std::exception& ex) { + throw std::runtime_error(std::string(test_port_offset_env_var) + " must be an unsigned integer: " + ex.what()); + } +} + +/** Apply the current test's compact port shard mapping to a base port. */ +inline uint16_t shard_port(uint16_t port) { + const uint32_t offset = test_port_offset(); + if(offset == 0) + return port; + + const uint32_t shard_base = compact_shard_anchor_port + offset; + uint32_t shifted_port = static_cast(port) + offset; + + switch(port) { + case 8788: + shifted_port = shard_base + compact_bios_http_slot; + break; + case 7899: + shifted_port = shard_base + compact_ship_slot; + break; + case default_state_history_port: + shifted_port = shard_base + compact_state_history_slot; + break; + case 9011: + shifted_port = shard_base + compact_alternate_service_slot; + break; + case 8888: + shifted_port = shard_base + compact_http_slot; + break; + default: + if(compact_alternate_first_port <= port && port <= compact_alternate_last_port) { + shifted_port = shard_base + compact_alternate_p2p_slot + (port - compact_alternate_first_port); + } else if(compact_p2p_first_port <= port && port <= compact_p2p_last_port) { + shifted_port = shard_base + compact_p2p_slot + (port - compact_p2p_first_port); + } else if(compact_wallet_first_port <= port && port <= compact_wallet_last_port) { + shifted_port = shard_base + compact_wallet_slot + std::min(port - compact_wallet_first_port, 99); + } + break; + } + + if(shifted_port > std::numeric_limits::max()) + throw std::runtime_error(std::string(test_port_offset_env_var) + " shifts the requested port outside uint16_t"); + return static_cast(shifted_port); +} + +/** Return the default state-history websocket endpoint for this test shard. */ +inline std::string default_state_history_endpoint() { + return std::string("127.0.0.1:") + std::to_string(shard_port(default_state_history_port)); +} + +} // namespace sysio::testing From 672520264980e1ebe24b26afaf6606f35d6ad8c1 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 5 Jun 2026 18:02:44 +0000 Subject: [PATCH 02/16] Restore NP labels for sharded tests --- tests/CMakeLists.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 40cd19889d..8e91caf39d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -133,9 +133,9 @@ endif() #To run plugin_test with all log from blockchain displayed, put --verbose after --, i.e. plugin_test -- --verbose add_p_test(NAME plugin_test COMMAND plugin_test --report_level=detailed --color_output) -add_p_test(NAME nodeop_sanity_test COMMAND tests/nodeop_run_test.py -v --sanity-test ${UNSHARE} PORT_OFFSET 1000) -add_p_test(NAME nodeop_run_test COMMAND tests/nodeop_run_test.py -v ${UNSHARE} PORT_OFFSET 2000) -add_p_test(NAME nodeop_lib_test COMMAND tests/nodeop_lib_test.py -n 4 -p 3 -s ring -v ${UNSHARE} PORT_OFFSET 3000) +add_np_test(NAME nodeop_sanity_test COMMAND tests/nodeop_run_test.py -v --sanity-test ${UNSHARE} PORT_OFFSET 1000) +add_np_test(NAME nodeop_run_test COMMAND tests/nodeop_run_test.py -v ${UNSHARE} PORT_OFFSET 2000) +add_np_test(NAME nodeop_lib_test COMMAND tests/nodeop_lib_test.py -n 4 -p 3 -s ring -v ${UNSHARE} PORT_OFFSET 3000) add_np_test(NAME block_log_util_test COMMAND tests/block_log_util_test.py -v ${UNSHARE}) add_np_test(NAME block_log_retain_blocks_test COMMAND tests/block_log_retain_blocks_test.py -v ${UNSHARE}) @@ -154,7 +154,7 @@ add_np_test(NAME cluster_launcher COMMAND tests/cluster_launcher.py -v ${UNSHARE add_np_test(NAME transition_to_if COMMAND tests/transition_to_if.py -v ${UNSHARE}) add_np_test(NAME disaster_recovery COMMAND tests/disaster_recovery.py -v ${UNSHARE}) -add_p_test(NAME disaster_recovery_2 COMMAND tests/disaster_recovery_2.py -v ${UNSHARE} PORT_OFFSET 6000) +add_np_test(NAME disaster_recovery_2 COMMAND tests/disaster_recovery_2.py -v ${UNSHARE} PORT_OFFSET 6000) add_np_test(NAME disaster_recovery_3 COMMAND tests/disaster_recovery_3.py -v ${UNSHARE}) add_np_test(NAME production_pause_max_rev_blks_test COMMAND tests/production_pause_max_rev_blks_test.py -v ${UNSHARE}) add_np_test(NAME production_pause_vote_timeout COMMAND tests/production_pause_vote_timeout.py -v ${UNSHARE}) @@ -164,7 +164,7 @@ add_np_test(NAME ship_reqs_across_svnn_test COMMAND tests/ship_reqs_across_svnn_ add_np_test(NAME ship_restart_test COMMAND tests/ship_restart_test.py -v ${UNSHARE}) add_np_test(NAME ship_test COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 ${UNSHARE}) add_np_test(NAME ship_test_unix COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 ${UNSHARE} --unix-socket) -add_p_test(NAME ship_kv_delta_test COMMAND tests/ship_kv_delta_test.py -v ${UNSHARE} PORT_OFFSET 7000) +add_np_test(NAME ship_kv_delta_test COMMAND tests/ship_kv_delta_test.py -v ${UNSHARE} PORT_OFFSET 7000) add_np_test(NAME get_kv_rows_test COMMAND tests/get_kv_rows_test.py -v ${UNSHARE}) add_lr_test(NAME ship_streamer_test COMMAND tests/ship_streamer_test.py -v --num-clients 10 ${UNSHARE}) @@ -211,11 +211,11 @@ add_p_test(NAME version-label-test COMMAND tests/version-label.sh "v${VERSION_FU add_p_test(NAME full-version-label-test COMMAND tests/full-version-label.sh "v${VERSION_FULL}" ${CMAKE_SOURCE_DIR}) add_np_test(NAME nested_container_multi_index_test COMMAND tests/nested_container_multi_index_test.py -n 2) -add_p_test(NAME p2p_multiple_listen_test COMMAND tests/p2p_multiple_listen_test.py -v ${UNSHARE} PORT_OFFSET 8000) -add_p_test(NAME p2p_no_listen_test COMMAND tests/p2p_no_listen_test.py -v ${UNSHARE} PORT_OFFSET 13000) +add_np_test(NAME p2p_multiple_listen_test COMMAND tests/p2p_multiple_listen_test.py -v ${UNSHARE} PORT_OFFSET 8000) +add_np_test(NAME p2p_no_listen_test COMMAND tests/p2p_no_listen_test.py -v ${UNSHARE} PORT_OFFSET 13000) add_lr_test(NAME p2p_sync_throttle_test COMMAND tests/p2p_sync_throttle_test.py -v -d 2 ${UNSHARE}) -add_p_test(NAME p2p_no_blocks_if_test COMMAND tests/p2p_no_blocks_test.py -v -d 2 ${UNSHARE} PORT_OFFSET 9000) -add_p_test(NAME p2p_peer_auth_test COMMAND tests/p2p_peer_auth_test.py -v ${UNSHARE} PORT_OFFSET 4000) +add_np_test(NAME p2p_no_blocks_if_test COMMAND tests/p2p_no_blocks_test.py -v -d 2 ${UNSHARE} PORT_OFFSET 9000) +add_np_test(NAME p2p_peer_auth_test COMMAND tests/p2p_peer_auth_test.py -v ${UNSHARE} PORT_OFFSET 4000) add_np_test(NAME p2p_peer_scoring_test COMMAND tests/p2p_peer_scoring_test.py -v ${UNSHARE}) # needs iproute-tc or iproute2 depending on platform @@ -226,7 +226,7 @@ add_np_test(NAME p2p_peer_scoring_test COMMAND tests/p2p_peer_scoring_test.py -v add_lr_test(NAME nodeop_forked_chain_lr_test COMMAND tests/nodeop_forked_chain_test.py -v ${UNSHARE}) -add_p_test(NAME nodeop_contrl_c_test COMMAND tests/nodeop_contrl_c_test.py -v ${UNSHARE} PORT_OFFSET 14000) +add_np_test(NAME nodeop_contrl_c_test COMMAND tests/nodeop_contrl_c_test.py -v ${UNSHARE} PORT_OFFSET 14000) add_lr_test(NAME nodeop_under_min_avail_ram_lr_test COMMAND tests/nodeop_under_min_avail_ram.py -v ${UNSHARE}) @@ -264,13 +264,13 @@ add_p_test(NAME sysio_util_bls_test COMMAND tests/sysio_util_bls_test.py) add_p_test(NAME clio_em_key_test COMMAND tests/clio_em_key_test.py) add_p_test(NAME sysio_util_snapshot_info_test COMMAND tests/sysio_util_snapshot_info_test.py) -add_p_test(NAME http_plugin_test COMMAND tests/http_plugin_test.py ${UNSHARE} TIMEOUT 200 PORT_OFFSET 5000) +add_np_test(NAME http_plugin_test COMMAND tests/http_plugin_test.py ${UNSHARE} TIMEOUT 200 PORT_OFFSET 5000) -add_p_test(NAME plugin_http_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100 PORT_OFFSET 10000) +add_np_test(NAME plugin_http_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100 PORT_OFFSET 10000) -add_p_test(NAME plugin_http_api_test_savanna COMMAND tests/plugin_http_api_test_savanna.py TIMEOUT 340 PORT_OFFSET 11000) +add_np_test(NAME plugin_http_api_test_savanna COMMAND tests/plugin_http_api_test_savanna.py TIMEOUT 340 PORT_OFFSET 11000) -add_p_test(NAME plugin_http_category_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100 PORT_OFFSET 12000) +add_np_test(NAME plugin_http_category_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100 PORT_OFFSET 12000) set_tests_properties(plugin_http_category_api_test PROPERTIES ENVIRONMENT "SYSIO_TEST_PORT_OFFSET=12000;PLUGIN_HTTP_TEST_CATEGORY=ON") From d4a8d83692ec76aa8f7ef396412077c4cd242279 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 5 Jun 2026 18:04:50 +0000 Subject: [PATCH 03/16] Remove unshare from CTest commands --- tests/CMakeLists.txt | 214 +++++++++++++++++++++---------------------- 1 file changed, 104 insertions(+), 110 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8e91caf39d..e3fcc5b3ec 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -119,12 +119,6 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/producer_rank_test.py ${CMAKE_CURRENT configure_file(${CMAKE_CURRENT_SOURCE_DIR}/split_blocklog_replay_test.py ${CMAKE_CURRENT_BINARY_DIR}/split_blocklog_replay_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/PerformanceHarnessScenarioRunner.py ${CMAKE_CURRENT_BINARY_DIR}/PerformanceHarnessScenarioRunner.py COPYONLY) -if(DEFINED ENV{GITHUB_ACTIONS}) - set(UNSHARE "--unshared") -else() - set(UNSHARE "") -endif() - option(SYSIO_ENABLE_RELEASE_BUILD_TEST "Enables a test that verifies nodeop was compiled with compiler options typical for a release build" On) if(SYSIO_ENABLE_RELEASE_BUILD_TEST) add_p_test(NAME release-build-test COMMAND tests/release-build.sh) @@ -133,11 +127,11 @@ endif() #To run plugin_test with all log from blockchain displayed, put --verbose after --, i.e. plugin_test -- --verbose add_p_test(NAME plugin_test COMMAND plugin_test --report_level=detailed --color_output) -add_np_test(NAME nodeop_sanity_test COMMAND tests/nodeop_run_test.py -v --sanity-test ${UNSHARE} PORT_OFFSET 1000) -add_np_test(NAME nodeop_run_test COMMAND tests/nodeop_run_test.py -v ${UNSHARE} PORT_OFFSET 2000) -add_np_test(NAME nodeop_lib_test COMMAND tests/nodeop_lib_test.py -n 4 -p 3 -s ring -v ${UNSHARE} PORT_OFFSET 3000) -add_np_test(NAME block_log_util_test COMMAND tests/block_log_util_test.py -v ${UNSHARE}) -add_np_test(NAME block_log_retain_blocks_test COMMAND tests/block_log_retain_blocks_test.py -v ${UNSHARE}) +add_np_test(NAME nodeop_sanity_test COMMAND tests/nodeop_run_test.py -v --sanity-test PORT_OFFSET 1000) +add_np_test(NAME nodeop_run_test COMMAND tests/nodeop_run_test.py -v PORT_OFFSET 2000) +add_np_test(NAME nodeop_lib_test COMMAND tests/nodeop_lib_test.py -n 4 -p 3 -s ring -v PORT_OFFSET 3000) +add_np_test(NAME block_log_util_test COMMAND tests/block_log_util_test.py -v) +add_np_test(NAME block_log_retain_blocks_test COMMAND tests/block_log_retain_blocks_test.py -v) add_subdirectory( TestHarness ) add_subdirectory( trx_generator ) @@ -149,74 +143,74 @@ target_link_libraries(ship_client sysio_chain Boost::program_options Boost::syst add_executable(ship_streamer ship_streamer.cpp) target_link_libraries(ship_streamer sysio_chain Boost::program_options Boost::system Boost::asio Boost::beast Threads::Threads) -add_np_test(NAME cluster_launcher COMMAND tests/cluster_launcher.py -v ${UNSHARE}) - -add_np_test(NAME transition_to_if COMMAND tests/transition_to_if.py -v ${UNSHARE}) - -add_np_test(NAME disaster_recovery COMMAND tests/disaster_recovery.py -v ${UNSHARE}) -add_np_test(NAME disaster_recovery_2 COMMAND tests/disaster_recovery_2.py -v ${UNSHARE} PORT_OFFSET 6000) -add_np_test(NAME disaster_recovery_3 COMMAND tests/disaster_recovery_3.py -v ${UNSHARE}) -add_np_test(NAME production_pause_max_rev_blks_test COMMAND tests/production_pause_max_rev_blks_test.py -v ${UNSHARE}) -add_np_test(NAME production_pause_vote_timeout COMMAND tests/production_pause_vote_timeout.py -v ${UNSHARE}) -add_np_test(NAME production_restart COMMAND tests/production_restart.py -v ${UNSHARE}) - -add_np_test(NAME ship_reqs_across_svnn_test COMMAND tests/ship_reqs_across_svnn_test.py -v ${UNSHARE}) -add_np_test(NAME ship_restart_test COMMAND tests/ship_restart_test.py -v ${UNSHARE}) -add_np_test(NAME ship_test COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 ${UNSHARE}) -add_np_test(NAME ship_test_unix COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 ${UNSHARE} --unix-socket) -add_np_test(NAME ship_kv_delta_test COMMAND tests/ship_kv_delta_test.py -v ${UNSHARE} PORT_OFFSET 7000) -add_np_test(NAME get_kv_rows_test COMMAND tests/get_kv_rows_test.py -v ${UNSHARE}) - -add_lr_test(NAME ship_streamer_test COMMAND tests/ship_streamer_test.py -v --num-clients 10 ${UNSHARE}) -add_lr_test(NAME ship_streamer_if_fetch_finality_data_test COMMAND tests/ship_streamer_test.py -v --num-clients 10 --finality-data-history ${UNSHARE}) -add_np_test(NAME ship_kill_client_test COMMAND tests/ship_kill_client_test.py -v --num-clients 20 ${UNSHARE}) - -add_np_test(NAME separate_prod_fin_test COMMAND tests/separate_prod_fin_test.py ${UNSHARE}) -add_np_test(NAME snapshot_in_svnn_transition_test COMMAND tests/snapshot_in_svnn_transition_test.py ${UNSHARE}) -add_np_test(NAME nodeop_protocol_feature_test COMMAND tests/nodeop_protocol_feature_test.py -v ${UNSHARE}) -add_np_test(NAME compute_transaction_test COMMAND tests/compute_transaction_test.py -v -p 2 -n 3 ${UNSHARE}) -add_np_test(NAME read-only-trx-basic-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 1 ${UNSHARE}) -add_np_test(NAME read-only-trx-parallel-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 16 --num-test-runs 2 ${UNSHARE}) -add_np_test(NAME read-only-trx-parallel-if-sys-vm-oc-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --sys-vm-oc-enable all --read-only-threads 16 --num-test-runs 2 --activate-if ${UNSHARE}) -add_np_test(NAME read-only-trx-parallel-no-oc-if-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --sys-vm-oc-enable none --read-only-threads 6 --num-test-runs 2 --activate-if ${UNSHARE}) -add_np_test(NAME interrupt-read-only-trx-basic-test COMMAND tests/interrupt_read_only_trx_test.py -p 2 -n 3 --read-only-threads 1 ${UNSHARE}) -add_np_test(NAME interrupt-read-only-trx-parallel-test COMMAND tests/interrupt_read_only_trx_test.py -p 2 -n 3 --read-only-threads 16 ${UNSHARE}) -add_np_test(NAME interrupt-read-only-trx-parallel-if-sys-vm-oc-test COMMAND tests/interrupt_read_only_trx_test.py -p 2 -n 3 --sys-vm-oc-enable all --read-only-threads 16 ${UNSHARE}) -add_np_test(NAME subjective_billing_test COMMAND tests/subjective_billing_test.py -v -p 2 -n 4 ${UNSHARE}) -add_np_test(NAME get_account_test COMMAND tests/get_account_test.py -v -p 2 -n 3 ${UNSHARE}) -add_np_test(NAME multisig_review_test COMMAND tests/multisig_review_test.py -v -p 1 -n 1 ${UNSHARE}) -add_np_test(NAME pause_at_block_test COMMAND tests/pause_at_block_test.py -v ${UNSHARE}) - -add_np_test(NAME distributed-transactions-test COMMAND tests/distributed-transactions-test.py -d 2 -p 4 -n 6 -v ${UNSHARE}) -add_np_test(NAME restart-scenarios-test-resync COMMAND tests/restart-scenarios-test.py -c resync -p4 -v ${UNSHARE}) -add_np_test(NAME restart-scenarios-test-hard_replay COMMAND tests/restart-scenarios-test.py -c hardReplay -p4 -v ${UNSHARE}) -add_np_test(NAME restart-scenarios-test-none COMMAND tests/restart-scenarios-test.py -c none --kill-sig term -p4 -v ${UNSHARE}) -add_np_test(NAME terminate-scenarios-test-resync COMMAND tests/terminate-scenarios-test.py -c resync --terminate-at-block 10 --kill-sig term ${UNSHARE}) -add_np_test(NAME terminate-scenarios-test-replay COMMAND tests/terminate-scenarios-test.py -c replay --terminate-at-block 10 --kill-sig term ${UNSHARE}) -add_np_test(NAME terminate-scenarios-test-hard_replay COMMAND tests/terminate-scenarios-test.py -c hardReplay --terminate-at-block 10 --kill-sig term ${UNSHARE}) -add_np_test(NAME terminate-scenarios-if-test-replay-pass-transition COMMAND tests/terminate-scenarios-test.py -c replay --terminate-at-block 150 --kill-sig term --activate-if ${UNSHARE}) -add_np_test(NAME terminate-scenarios-if-test-hard_replay-pass-transition COMMAND tests/terminate-scenarios-test.py -c hardReplay --terminate-at-block 150 --kill-sig term --activate-if ${UNSHARE}) -add_np_test(NAME validate_dirty_db_test COMMAND tests/validate-dirty-db.py -v ${UNSHARE}) +add_np_test(NAME cluster_launcher COMMAND tests/cluster_launcher.py -v) + +add_np_test(NAME transition_to_if COMMAND tests/transition_to_if.py -v) + +add_np_test(NAME disaster_recovery COMMAND tests/disaster_recovery.py -v) +add_np_test(NAME disaster_recovery_2 COMMAND tests/disaster_recovery_2.py -v PORT_OFFSET 6000) +add_np_test(NAME disaster_recovery_3 COMMAND tests/disaster_recovery_3.py -v) +add_np_test(NAME production_pause_max_rev_blks_test COMMAND tests/production_pause_max_rev_blks_test.py -v) +add_np_test(NAME production_pause_vote_timeout COMMAND tests/production_pause_vote_timeout.py -v) +add_np_test(NAME production_restart COMMAND tests/production_restart.py -v) + +add_np_test(NAME ship_reqs_across_svnn_test COMMAND tests/ship_reqs_across_svnn_test.py -v) +add_np_test(NAME ship_restart_test COMMAND tests/ship_restart_test.py -v) +add_np_test(NAME ship_test COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000) +add_np_test(NAME ship_test_unix COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 --unix-socket) +add_np_test(NAME ship_kv_delta_test COMMAND tests/ship_kv_delta_test.py -v PORT_OFFSET 7000) +add_np_test(NAME get_kv_rows_test COMMAND tests/get_kv_rows_test.py -v) + +add_lr_test(NAME ship_streamer_test COMMAND tests/ship_streamer_test.py -v --num-clients 10) +add_lr_test(NAME ship_streamer_if_fetch_finality_data_test COMMAND tests/ship_streamer_test.py -v --num-clients 10 --finality-data-history) +add_np_test(NAME ship_kill_client_test COMMAND tests/ship_kill_client_test.py -v --num-clients 20) + +add_np_test(NAME separate_prod_fin_test COMMAND tests/separate_prod_fin_test.py) +add_np_test(NAME snapshot_in_svnn_transition_test COMMAND tests/snapshot_in_svnn_transition_test.py) +add_np_test(NAME nodeop_protocol_feature_test COMMAND tests/nodeop_protocol_feature_test.py -v) +add_np_test(NAME compute_transaction_test COMMAND tests/compute_transaction_test.py -v -p 2 -n 3) +add_np_test(NAME read-only-trx-basic-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 1) +add_np_test(NAME read-only-trx-parallel-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --read-only-threads 16 --num-test-runs 2) +add_np_test(NAME read-only-trx-parallel-if-sys-vm-oc-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --sys-vm-oc-enable all --read-only-threads 16 --num-test-runs 2 --activate-if) +add_np_test(NAME read-only-trx-parallel-no-oc-if-test COMMAND tests/read_only_trx_test.py -p 2 -n 3 --sys-vm-oc-enable none --read-only-threads 6 --num-test-runs 2 --activate-if) +add_np_test(NAME interrupt-read-only-trx-basic-test COMMAND tests/interrupt_read_only_trx_test.py -p 2 -n 3 --read-only-threads 1) +add_np_test(NAME interrupt-read-only-trx-parallel-test COMMAND tests/interrupt_read_only_trx_test.py -p 2 -n 3 --read-only-threads 16) +add_np_test(NAME interrupt-read-only-trx-parallel-if-sys-vm-oc-test COMMAND tests/interrupt_read_only_trx_test.py -p 2 -n 3 --sys-vm-oc-enable all --read-only-threads 16) +add_np_test(NAME subjective_billing_test COMMAND tests/subjective_billing_test.py -v -p 2 -n 4) +add_np_test(NAME get_account_test COMMAND tests/get_account_test.py -v -p 2 -n 3) +add_np_test(NAME multisig_review_test COMMAND tests/multisig_review_test.py -v -p 1 -n 1) +add_np_test(NAME pause_at_block_test COMMAND tests/pause_at_block_test.py -v) + +add_np_test(NAME distributed-transactions-test COMMAND tests/distributed-transactions-test.py -d 2 -p 4 -n 6 -v) +add_np_test(NAME restart-scenarios-test-resync COMMAND tests/restart-scenarios-test.py -c resync -p4 -v) +add_np_test(NAME restart-scenarios-test-hard_replay COMMAND tests/restart-scenarios-test.py -c hardReplay -p4 -v) +add_np_test(NAME restart-scenarios-test-none COMMAND tests/restart-scenarios-test.py -c none --kill-sig term -p4 -v) +add_np_test(NAME terminate-scenarios-test-resync COMMAND tests/terminate-scenarios-test.py -c resync --terminate-at-block 10 --kill-sig term) +add_np_test(NAME terminate-scenarios-test-replay COMMAND tests/terminate-scenarios-test.py -c replay --terminate-at-block 10 --kill-sig term) +add_np_test(NAME terminate-scenarios-test-hard_replay COMMAND tests/terminate-scenarios-test.py -c hardReplay --terminate-at-block 10 --kill-sig term) +add_np_test(NAME terminate-scenarios-if-test-replay-pass-transition COMMAND tests/terminate-scenarios-test.py -c replay --terminate-at-block 150 --kill-sig term --activate-if) +add_np_test(NAME terminate-scenarios-if-test-hard_replay-pass-transition COMMAND tests/terminate-scenarios-test.py -c hardReplay --terminate-at-block 150 --kill-sig term --activate-if) +add_np_test(NAME validate_dirty_db_test COMMAND tests/validate-dirty-db.py -v) add_np_test(NAME kiod_auto_launch_test COMMAND tests/kiod_auto_launch_test.py) -add_np_test(NAME nodeop_snapshot_diff_test COMMAND tests/nodeop_snapshot_diff_test.py -v ${UNSHARE}) -add_np_test(NAME nodeop_snapshot_forked_test COMMAND tests/nodeop_snapshot_forked_test.py -v ${UNSHARE}) -add_np_test(NAME nodeop_late_block_test COMMAND tests/nodeop_late_block_test.py -v ${UNSHARE}) +add_np_test(NAME nodeop_snapshot_diff_test COMMAND tests/nodeop_snapshot_diff_test.py -v) +add_np_test(NAME nodeop_snapshot_forked_test COMMAND tests/nodeop_snapshot_forked_test.py -v) +add_np_test(NAME nodeop_late_block_test COMMAND tests/nodeop_late_block_test.py -v) -add_np_test(NAME trx_finality_status_test COMMAND tests/trx_finality_status_test.py -v ${UNSHARE}) +add_np_test(NAME trx_finality_status_test COMMAND tests/trx_finality_status_test.py -v) -add_np_test(NAME trx_finality_status_forked_test COMMAND tests/trx_finality_status_forked_test.py -v ${UNSHARE}) +add_np_test(NAME trx_finality_status_forked_test COMMAND tests/trx_finality_status_forked_test.py -v) add_p_test(NAME db_modes_test COMMAND tests/db_modes_test.sh -v COST 6000) add_p_test(NAME version-label-test COMMAND tests/version-label.sh "v${VERSION_FULL}") add_p_test(NAME full-version-label-test COMMAND tests/full-version-label.sh "v${VERSION_FULL}" ${CMAKE_SOURCE_DIR}) add_np_test(NAME nested_container_multi_index_test COMMAND tests/nested_container_multi_index_test.py -n 2) -add_np_test(NAME p2p_multiple_listen_test COMMAND tests/p2p_multiple_listen_test.py -v ${UNSHARE} PORT_OFFSET 8000) -add_np_test(NAME p2p_no_listen_test COMMAND tests/p2p_no_listen_test.py -v ${UNSHARE} PORT_OFFSET 13000) -add_lr_test(NAME p2p_sync_throttle_test COMMAND tests/p2p_sync_throttle_test.py -v -d 2 ${UNSHARE}) -add_np_test(NAME p2p_no_blocks_if_test COMMAND tests/p2p_no_blocks_test.py -v -d 2 ${UNSHARE} PORT_OFFSET 9000) -add_np_test(NAME p2p_peer_auth_test COMMAND tests/p2p_peer_auth_test.py -v ${UNSHARE} PORT_OFFSET 4000) -add_np_test(NAME p2p_peer_scoring_test COMMAND tests/p2p_peer_scoring_test.py -v ${UNSHARE}) +add_np_test(NAME p2p_multiple_listen_test COMMAND tests/p2p_multiple_listen_test.py -v PORT_OFFSET 8000) +add_np_test(NAME p2p_no_listen_test COMMAND tests/p2p_no_listen_test.py -v PORT_OFFSET 13000) +add_lr_test(NAME p2p_sync_throttle_test COMMAND tests/p2p_sync_throttle_test.py -v -d 2) +add_np_test(NAME p2p_no_blocks_if_test COMMAND tests/p2p_no_blocks_test.py -v -d 2 PORT_OFFSET 9000) +add_np_test(NAME p2p_peer_auth_test COMMAND tests/p2p_peer_auth_test.py -v PORT_OFFSET 4000) +add_np_test(NAME p2p_peer_scoring_test COMMAND tests/p2p_peer_scoring_test.py -v) # needs iproute-tc or iproute2 depending on platform #add_np_test(NAME p2p_high_latency_test COMMAND tests/p2p_high_latency_test.py -v) @@ -224,47 +218,47 @@ add_np_test(NAME p2p_peer_scoring_test COMMAND tests/p2p_peer_scoring_test.py -v # This test is too much for CI/CD machines. We do run it with fewer nodes as a nonparallelizable_tests above #add_lr_test(NAME distributed_transactions_lr_test COMMAND tests/distributed-transactions-test.py -d 2 -p 21 -n 21 -v) -add_lr_test(NAME nodeop_forked_chain_lr_test COMMAND tests/nodeop_forked_chain_test.py -v ${UNSHARE}) +add_lr_test(NAME nodeop_forked_chain_lr_test COMMAND tests/nodeop_forked_chain_test.py -v) -add_np_test(NAME nodeop_contrl_c_test COMMAND tests/nodeop_contrl_c_test.py -v ${UNSHARE} PORT_OFFSET 14000) +add_np_test(NAME nodeop_contrl_c_test COMMAND tests/nodeop_contrl_c_test.py -v PORT_OFFSET 14000) -add_lr_test(NAME nodeop_under_min_avail_ram_lr_test COMMAND tests/nodeop_under_min_avail_ram.py -v ${UNSHARE}) +add_lr_test(NAME nodeop_under_min_avail_ram_lr_test COMMAND tests/nodeop_under_min_avail_ram.py -v) -add_lr_test(NAME nodeop_irreversible_mode_lr_test COMMAND tests/nodeop_irreversible_mode_test.py -v ${UNSHARE}) +add_lr_test(NAME nodeop_irreversible_mode_lr_test COMMAND tests/nodeop_irreversible_mode_test.py -v) -add_lr_test(NAME nodeop_read_terminate_at_block_lr_test COMMAND tests/nodeop_read_terminate_at_block_test.py -v ${UNSHARE}) +add_lr_test(NAME nodeop_read_terminate_at_block_lr_test COMMAND tests/nodeop_read_terminate_at_block_test.py -v) -add_np_test(NAME liveness_test COMMAND tests/liveness_test.py -v ${UNSHARE}) +add_np_test(NAME liveness_test COMMAND tests/liveness_test.py -v) -add_np_test(NAME nodeop_chainbase_allocation_test COMMAND tests/nodeop_chainbase_allocation_test.py -v ${UNSHARE}) +add_np_test(NAME nodeop_chainbase_allocation_test COMMAND tests/nodeop_chainbase_allocation_test.py -v) -add_np_test(NAME nodeop_signal_throw_test COMMAND tests/nodeop_signal_throw_test.py -v ${UNSHARE}) +add_np_test(NAME nodeop_signal_throw_test COMMAND tests/nodeop_signal_throw_test.py -v) -add_np_test(NAME metamask_trx_signing_test COMMAND tests/metamask/push_metamask_trx.py --simulate -v ${UNSHARE}) +add_np_test(NAME metamask_trx_signing_test COMMAND tests/metamask/push_metamask_trx.py --simulate -v) -add_lr_test(NAME nodeop_startup_catchup_lr_test COMMAND tests/nodeop_startup_catchup.py -v ${UNSHARE}) +add_lr_test(NAME nodeop_startup_catchup_lr_test COMMAND tests/nodeop_startup_catchup.py -v) -add_np_test(NAME nodeop_short_fork_take_over_test COMMAND tests/nodeop_short_fork_take_over_test.py -v ${UNSHARE}) +add_np_test(NAME nodeop_short_fork_take_over_test COMMAND tests/nodeop_short_fork_take_over_test.py -v) -add_np_test(NAME nodeop_extra_packed_data_test COMMAND tests/nodeop_extra_packed_data_test.py -v -p 2 ${UNSHARE}) +add_np_test(NAME nodeop_extra_packed_data_test COMMAND tests/nodeop_extra_packed_data_test.py -v -p 2) -add_lr_test(NAME nodeop_producer_watermark_lr_test COMMAND tests/nodeop_producer_watermark_test.py -v ${UNSHARE}) +add_lr_test(NAME nodeop_producer_watermark_lr_test COMMAND tests/nodeop_producer_watermark_test.py -v) -add_lr_test(NAME nodeop_high_transaction_lr_test COMMAND tests/nodeop_high_transaction_test.py -p 4 -n 8 --num-transactions 10000 --max-transactions-per-second 500 ${UNSHARE}) +add_lr_test(NAME nodeop_high_transaction_lr_test COMMAND tests/nodeop_high_transaction_test.py -p 4 -n 8 --num-transactions 10000 --max-transactions-per-second 500) -add_lr_test(NAME nodeop_retry_transaction_lr_test COMMAND tests/nodeop_retry_transaction_test.py -v --num-transactions 100 --max-transactions-per-second 10 --total-accounts 5 ${UNSHARE}) +add_lr_test(NAME nodeop_retry_transaction_lr_test COMMAND tests/nodeop_retry_transaction_test.py -v --num-transactions 100 --max-transactions-per-second 10 --total-accounts 5) add_np_test(NAME cli_test COMMAND tests/cli_test.py) -add_np_test(NAME lib_advance_test COMMAND tests/lib_advance_test.py -v ${UNSHARE}) +add_np_test(NAME lib_advance_test COMMAND tests/lib_advance_test.py -v) -add_np_test(NAME producer_rank_test COMMAND tests/producer_rank_test.py -v ${UNSHARE}) +add_np_test(NAME producer_rank_test COMMAND tests/producer_rank_test.py -v) add_p_test(NAME sysio_util_bls_test COMMAND tests/sysio_util_bls_test.py) add_p_test(NAME clio_em_key_test COMMAND tests/clio_em_key_test.py) add_p_test(NAME sysio_util_snapshot_info_test COMMAND tests/sysio_util_snapshot_info_test.py) -add_np_test(NAME http_plugin_test COMMAND tests/http_plugin_test.py ${UNSHARE} TIMEOUT 200 PORT_OFFSET 5000) +add_np_test(NAME http_plugin_test COMMAND tests/http_plugin_test.py TIMEOUT 200 PORT_OFFSET 5000) add_np_test(NAME plugin_http_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100 PORT_OFFSET 10000) @@ -278,28 +272,28 @@ add_np_test(NAME trace_plugin_test COMMAND tests/trace_plugin_test.py -v TIMEOUT add_lr_test(NAME resource_monitor_plugin_test COMMAND tests/resource_monitor_plugin_test.py -v) -add_lr_test(NAME nodeop_repeat_transaction_lr_test COMMAND tests/nodeop_high_transaction_test.py -v -p 4 -n 8 --num-transactions 1000 --max-transactions-per-second 500 --send-duplicates ${UNSHARE}) - -add_np_test(NAME light_validation_sync_test COMMAND tests/light_validation_sync_test.py -v ${UNSHARE}) -add_np_test(NAME proto_abi_test COMMAND tests/proto_abi_test.py -v ${UNSHARE}) -add_np_test(NAME nodeownreg_test COMMAND tests/nodeownreg_test.py -v ${UNSHARE}) -add_np_test(NAME interrupt_trx_test COMMAND tests/interrupt_trx_test.py -v ${UNSHARE}) - -add_lr_test(NAME auto_bp_peering_test COMMAND tests/auto_bp_peering_test.py -v ${UNSHARE}) -add_lr_test(NAME auto_bp_gossip_peering_test COMMAND tests/auto_bp_gossip_peering_test.py -v ${UNSHARE}) - -add_lr_test(NAME performance_test_bp COMMAND tests/PerformanceHarnessScenarioRunner.py findMax testBpOpMode --max-tps-to-test 50 --test-iteration-min-step 10 --test-iteration-duration-sec 10 --final-iterations-duration-sec 10 --calc-chain-threads lmax overrideBasicTestConfig -v --tps-limit-per-generator 25 --chain-state-db-size-mb 200 ${UNSHARE}) -add_lr_test(NAME performance_test_api COMMAND tests/PerformanceHarnessScenarioRunner.py findMax testApiOpMode --max-tps-to-test 50 --test-iteration-min-step 10 --test-iteration-duration-sec 10 --final-iterations-duration-sec 10 --calc-chain-threads lmax overrideBasicTestConfig -v --tps-limit-per-generator 25 --chain-state-db-size-mb 200 ${UNSHARE}) -add_lr_test(NAME performance_test_read_only_trxs COMMAND tests/PerformanceHarnessScenarioRunner.py findMax testApiOpMode --max-tps-to-test 50 --test-iteration-min-step 10 --test-iteration-duration-sec 10 --final-iterations-duration-sec 10 overrideBasicTestConfig -v --tps-limit-per-generator 25 --api-nodes-read-only-threads 2 --read-only-write-window-time-us 1000 --read-only-read-window-time-us 375000 --account-name "payloadless" --abi-file payloadless.abi --wasm-file payloadless.wasm --contract-dir unittests/test-contracts/payloadless --user-trx-data-file tests/PerformanceHarness/readOnlySlowTrxData.json --chain-state-db-size-mb 200 ${UNSHARE}) -add_lr_test(NAME performance_test_cpu_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py findMax testBpOpMode --max-tps-to-test 50 --test-iteration-min-step 10 --test-iteration-duration-sec 10 --final-iterations-duration-sec 10 overrideBasicTestConfig -v --tps-limit-per-generator 25 --chain-state-db-size-mb 200 --account-name "c" --abi-file sysmechanics.abi --wasm-file sysmechanics.wasm --contract-dir unittests/system-test-contracts/sysio.mechanics --user-trx-data-file tests/PerformanceHarness/cpuTrxData.json ${UNSHARE}) -add_np_test(NAME performance_test_basic_p2p COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 ${UNSHARE}) -add_np_test(NAME performance_test_basic_http COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --endpoint-mode http --producer-nodes 1 --validation-nodes 1 --api-nodes 1 --target-tps 10 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 ${UNSHARE}) -add_np_test(NAME performance_test_basic_transfer_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 --user-trx-data-file tests/PerformanceHarness/userTrxDataTransfer.json ${UNSHARE}) -add_np_test(NAME performance_test_basic_new_acct_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 --user-trx-data-file tests/PerformanceHarness/userTrxDataNewAccount.json ${UNSHARE}) -add_np_test(NAME performance_test_basic_cpu_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 --account-name "c" --abi-file sysmechanics.abi --wasm-file sysmechanics.wasm --contract-dir unittests/system-test-contracts/sysio.mechanics --user-trx-data-file tests/PerformanceHarness/cpuTrxData.json ${UNSHARE}) -add_np_test(NAME performance_test_basic_ram_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 --account-name "r" --abi-file sysmechanics.abi --wasm-file sysmechanics.wasm --contract-dir unittests/system-test-contracts/sysio.mechanics --user-trx-data-file tests/PerformanceHarness/ramTrxData.json ${UNSHARE}) -add_np_test(NAME performance_test_basic_net_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 --account-name "n" --abi-file sysmechanics.abi --wasm-file sysmechanics.wasm --contract-dir unittests/system-test-contracts/sysio.mechanics --user-trx-data-file tests/PerformanceHarness/netTrxData.json ${UNSHARE}) -add_np_test(NAME performance_test_basic_read_only_trxs COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --endpoint-mode http --producer-nodes 1 --validation-nodes 1 --api-nodes 1 --api-nodes-read-only-threads 2 --read-only-write-window-time-us 1000 --read-only-read-window-time-us 375000 --target-tps 10 --tps-limit-per-generator 5 --test-duration-sec 5 --chain-state-db-size-mb 200 --account-name "payloadless" --abi-file payloadless.abi --wasm-file payloadless.wasm --contract-dir unittests/test-contracts/payloadless --user-trx-data-file tests/PerformanceHarness/readOnlySlowTrxData.json ${UNSHARE}) +add_lr_test(NAME nodeop_repeat_transaction_lr_test COMMAND tests/nodeop_high_transaction_test.py -v -p 4 -n 8 --num-transactions 1000 --max-transactions-per-second 500 --send-duplicates) + +add_np_test(NAME light_validation_sync_test COMMAND tests/light_validation_sync_test.py -v) +add_np_test(NAME proto_abi_test COMMAND tests/proto_abi_test.py -v) +add_np_test(NAME nodeownreg_test COMMAND tests/nodeownreg_test.py -v) +add_np_test(NAME interrupt_trx_test COMMAND tests/interrupt_trx_test.py -v) + +add_lr_test(NAME auto_bp_peering_test COMMAND tests/auto_bp_peering_test.py -v) +add_lr_test(NAME auto_bp_gossip_peering_test COMMAND tests/auto_bp_gossip_peering_test.py -v) + +add_lr_test(NAME performance_test_bp COMMAND tests/PerformanceHarnessScenarioRunner.py findMax testBpOpMode --max-tps-to-test 50 --test-iteration-min-step 10 --test-iteration-duration-sec 10 --final-iterations-duration-sec 10 --calc-chain-threads lmax overrideBasicTestConfig -v --tps-limit-per-generator 25 --chain-state-db-size-mb 200) +add_lr_test(NAME performance_test_api COMMAND tests/PerformanceHarnessScenarioRunner.py findMax testApiOpMode --max-tps-to-test 50 --test-iteration-min-step 10 --test-iteration-duration-sec 10 --final-iterations-duration-sec 10 --calc-chain-threads lmax overrideBasicTestConfig -v --tps-limit-per-generator 25 --chain-state-db-size-mb 200) +add_lr_test(NAME performance_test_read_only_trxs COMMAND tests/PerformanceHarnessScenarioRunner.py findMax testApiOpMode --max-tps-to-test 50 --test-iteration-min-step 10 --test-iteration-duration-sec 10 --final-iterations-duration-sec 10 overrideBasicTestConfig -v --tps-limit-per-generator 25 --api-nodes-read-only-threads 2 --read-only-write-window-time-us 1000 --read-only-read-window-time-us 375000 --account-name "payloadless" --abi-file payloadless.abi --wasm-file payloadless.wasm --contract-dir unittests/test-contracts/payloadless --user-trx-data-file tests/PerformanceHarness/readOnlySlowTrxData.json --chain-state-db-size-mb 200) +add_lr_test(NAME performance_test_cpu_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py findMax testBpOpMode --max-tps-to-test 50 --test-iteration-min-step 10 --test-iteration-duration-sec 10 --final-iterations-duration-sec 10 overrideBasicTestConfig -v --tps-limit-per-generator 25 --chain-state-db-size-mb 200 --account-name "c" --abi-file sysmechanics.abi --wasm-file sysmechanics.wasm --contract-dir unittests/system-test-contracts/sysio.mechanics --user-trx-data-file tests/PerformanceHarness/cpuTrxData.json) +add_np_test(NAME performance_test_basic_p2p COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200) +add_np_test(NAME performance_test_basic_http COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --endpoint-mode http --producer-nodes 1 --validation-nodes 1 --api-nodes 1 --target-tps 10 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200) +add_np_test(NAME performance_test_basic_transfer_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 --user-trx-data-file tests/PerformanceHarness/userTrxDataTransfer.json) +add_np_test(NAME performance_test_basic_new_acct_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 --user-trx-data-file tests/PerformanceHarness/userTrxDataNewAccount.json) +add_np_test(NAME performance_test_basic_cpu_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 --account-name "c" --abi-file sysmechanics.abi --wasm-file sysmechanics.wasm --contract-dir unittests/system-test-contracts/sysio.mechanics --user-trx-data-file tests/PerformanceHarness/cpuTrxData.json) +add_np_test(NAME performance_test_basic_ram_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 --account-name "r" --abi-file sysmechanics.abi --wasm-file sysmechanics.wasm --contract-dir unittests/system-test-contracts/sysio.mechanics --user-trx-data-file tests/PerformanceHarness/ramTrxData.json) +add_np_test(NAME performance_test_basic_net_trx_spec COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --producer-nodes 1 --validation-nodes 1 --target-tps 20 --tps-limit-per-generator 10 --test-duration-sec 5 --chain-state-db-size-mb 200 --account-name "n" --abi-file sysmechanics.abi --wasm-file sysmechanics.wasm --contract-dir unittests/system-test-contracts/sysio.mechanics --user-trx-data-file tests/PerformanceHarness/netTrxData.json) +add_np_test(NAME performance_test_basic_read_only_trxs COMMAND tests/PerformanceHarnessScenarioRunner.py singleTest -v --endpoint-mode http --producer-nodes 1 --validation-nodes 1 --api-nodes 1 --api-nodes-read-only-threads 2 --read-only-write-window-time-us 1000 --read-only-read-window-time-us 375000 --target-tps 10 --tps-limit-per-generator 5 --test-duration-sec 5 --chain-state-db-size-mb 200 --account-name "payloadless" --abi-file payloadless.abi --wasm-file payloadless.wasm --contract-dir unittests/test-contracts/payloadless --user-trx-data-file tests/PerformanceHarness/readOnlySlowTrxData.json) if(ENABLE_COVERAGE_TESTING) From c3839d67653db50693714f9e2a234859f7972b3d Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 5 Jun 2026 18:09:52 +0000 Subject: [PATCH 04/16] Auto-assign sharded test port offsets --- cmake/test-helpers.cmake | 8 ++++++-- tests/CMakeLists.txt | 31 +++++++++++++++---------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/cmake/test-helpers.cmake b/cmake/test-helpers.cmake index 462261bc3e..fa21a38246 100644 --- a/cmake/test-helpers.cmake +++ b/cmake/test-helpers.cmake @@ -13,7 +13,7 @@ function(next_test_port_offset out_var) endfunction() function(setup_test_common) - cmake_parse_arguments(PARSE_ARGV 0 arg "AUTO_PORT_OFFSET;AUTO_LR_PORT_OFFSET" "NAME;COST;TIMEOUT;PORT_OFFSET" "COMMAND") + cmake_parse_arguments(PARSE_ARGV 0 arg "AUTO_PORT_OFFSET;AUTO_LR_PORT_OFFSET" "NAME;COST;TIMEOUT;PORT_OFFSET" "COMMAND;ENVIRONMENT") add_test(NAME "${arg_NAME}" COMMAND ${arg_COMMAND} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") @@ -30,8 +30,12 @@ function(setup_test_common) elseif(arg_AUTO_PORT_OFFSET) next_test_port_offset(test_port_offset) endif() + set(test_environment ${arg_ENVIRONMENT}) if(DEFINED test_port_offset) - set_tests_properties("${arg_NAME}" PROPERTIES ENVIRONMENT "SYSIO_TEST_PORT_OFFSET=${test_port_offset}") + list(APPEND test_environment "SYSIO_TEST_PORT_OFFSET=${test_port_offset}") + endif() + if(test_environment) + set_tests_properties("${arg_NAME}" PROPERTIES ENVIRONMENT "${test_environment}") endif() endfunction() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e3fcc5b3ec..bac7bca3dd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -127,9 +127,9 @@ endif() #To run plugin_test with all log from blockchain displayed, put --verbose after --, i.e. plugin_test -- --verbose add_p_test(NAME plugin_test COMMAND plugin_test --report_level=detailed --color_output) -add_np_test(NAME nodeop_sanity_test COMMAND tests/nodeop_run_test.py -v --sanity-test PORT_OFFSET 1000) -add_np_test(NAME nodeop_run_test COMMAND tests/nodeop_run_test.py -v PORT_OFFSET 2000) -add_np_test(NAME nodeop_lib_test COMMAND tests/nodeop_lib_test.py -n 4 -p 3 -s ring -v PORT_OFFSET 3000) +add_np_test(NAME nodeop_sanity_test COMMAND tests/nodeop_run_test.py -v --sanity-test) +add_np_test(NAME nodeop_run_test COMMAND tests/nodeop_run_test.py -v) +add_np_test(NAME nodeop_lib_test COMMAND tests/nodeop_lib_test.py -n 4 -p 3 -s ring -v) add_np_test(NAME block_log_util_test COMMAND tests/block_log_util_test.py -v) add_np_test(NAME block_log_retain_blocks_test COMMAND tests/block_log_retain_blocks_test.py -v) @@ -148,7 +148,7 @@ add_np_test(NAME cluster_launcher COMMAND tests/cluster_launcher.py -v) add_np_test(NAME transition_to_if COMMAND tests/transition_to_if.py -v) add_np_test(NAME disaster_recovery COMMAND tests/disaster_recovery.py -v) -add_np_test(NAME disaster_recovery_2 COMMAND tests/disaster_recovery_2.py -v PORT_OFFSET 6000) +add_np_test(NAME disaster_recovery_2 COMMAND tests/disaster_recovery_2.py -v) add_np_test(NAME disaster_recovery_3 COMMAND tests/disaster_recovery_3.py -v) add_np_test(NAME production_pause_max_rev_blks_test COMMAND tests/production_pause_max_rev_blks_test.py -v) add_np_test(NAME production_pause_vote_timeout COMMAND tests/production_pause_vote_timeout.py -v) @@ -158,7 +158,7 @@ add_np_test(NAME ship_reqs_across_svnn_test COMMAND tests/ship_reqs_across_svnn_ add_np_test(NAME ship_restart_test COMMAND tests/ship_restart_test.py -v) add_np_test(NAME ship_test COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000) add_np_test(NAME ship_test_unix COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 --unix-socket) -add_np_test(NAME ship_kv_delta_test COMMAND tests/ship_kv_delta_test.py -v PORT_OFFSET 7000) +add_np_test(NAME ship_kv_delta_test COMMAND tests/ship_kv_delta_test.py -v) add_np_test(NAME get_kv_rows_test COMMAND tests/get_kv_rows_test.py -v) add_lr_test(NAME ship_streamer_test COMMAND tests/ship_streamer_test.py -v --num-clients 10) @@ -205,11 +205,11 @@ add_p_test(NAME version-label-test COMMAND tests/version-label.sh "v${VERSION_FU add_p_test(NAME full-version-label-test COMMAND tests/full-version-label.sh "v${VERSION_FULL}" ${CMAKE_SOURCE_DIR}) add_np_test(NAME nested_container_multi_index_test COMMAND tests/nested_container_multi_index_test.py -n 2) -add_np_test(NAME p2p_multiple_listen_test COMMAND tests/p2p_multiple_listen_test.py -v PORT_OFFSET 8000) -add_np_test(NAME p2p_no_listen_test COMMAND tests/p2p_no_listen_test.py -v PORT_OFFSET 13000) +add_np_test(NAME p2p_multiple_listen_test COMMAND tests/p2p_multiple_listen_test.py -v) +add_np_test(NAME p2p_no_listen_test COMMAND tests/p2p_no_listen_test.py -v) add_lr_test(NAME p2p_sync_throttle_test COMMAND tests/p2p_sync_throttle_test.py -v -d 2) -add_np_test(NAME p2p_no_blocks_if_test COMMAND tests/p2p_no_blocks_test.py -v -d 2 PORT_OFFSET 9000) -add_np_test(NAME p2p_peer_auth_test COMMAND tests/p2p_peer_auth_test.py -v PORT_OFFSET 4000) +add_np_test(NAME p2p_no_blocks_if_test COMMAND tests/p2p_no_blocks_test.py -v -d 2) +add_np_test(NAME p2p_peer_auth_test COMMAND tests/p2p_peer_auth_test.py -v) add_np_test(NAME p2p_peer_scoring_test COMMAND tests/p2p_peer_scoring_test.py -v) # needs iproute-tc or iproute2 depending on platform @@ -220,7 +220,7 @@ add_np_test(NAME p2p_peer_scoring_test COMMAND tests/p2p_peer_scoring_test.py -v add_lr_test(NAME nodeop_forked_chain_lr_test COMMAND tests/nodeop_forked_chain_test.py -v) -add_np_test(NAME nodeop_contrl_c_test COMMAND tests/nodeop_contrl_c_test.py -v PORT_OFFSET 14000) +add_np_test(NAME nodeop_contrl_c_test COMMAND tests/nodeop_contrl_c_test.py -v) add_lr_test(NAME nodeop_under_min_avail_ram_lr_test COMMAND tests/nodeop_under_min_avail_ram.py -v) @@ -258,15 +258,14 @@ add_p_test(NAME sysio_util_bls_test COMMAND tests/sysio_util_bls_test.py) add_p_test(NAME clio_em_key_test COMMAND tests/clio_em_key_test.py) add_p_test(NAME sysio_util_snapshot_info_test COMMAND tests/sysio_util_snapshot_info_test.py) -add_np_test(NAME http_plugin_test COMMAND tests/http_plugin_test.py TIMEOUT 200 PORT_OFFSET 5000) +add_np_test(NAME http_plugin_test COMMAND tests/http_plugin_test.py TIMEOUT 200) -add_np_test(NAME plugin_http_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100 PORT_OFFSET 10000) +add_np_test(NAME plugin_http_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100) -add_np_test(NAME plugin_http_api_test_savanna COMMAND tests/plugin_http_api_test_savanna.py TIMEOUT 340 PORT_OFFSET 11000) +add_np_test(NAME plugin_http_api_test_savanna COMMAND tests/plugin_http_api_test_savanna.py TIMEOUT 340) -add_np_test(NAME plugin_http_category_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100 PORT_OFFSET 12000) -set_tests_properties(plugin_http_category_api_test PROPERTIES ENVIRONMENT - "SYSIO_TEST_PORT_OFFSET=12000;PLUGIN_HTTP_TEST_CATEGORY=ON") +add_np_test(NAME plugin_http_category_api_test COMMAND tests/plugin_http_api_test.py TIMEOUT 100 ENVIRONMENT + PLUGIN_HTTP_TEST_CATEGORY=ON) add_np_test(NAME trace_plugin_test COMMAND tests/trace_plugin_test.py -v TIMEOUT 400) From 8a09b81ef61f8e98e23669806d4418ce19eed42a Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 5 Jun 2026 21:24:11 +0000 Subject: [PATCH 05/16] Shard NP and LR test ports by category --- cmake/test-helpers.cmake | 2 +- docs/port-sharding-test-plan.md | 190 +++++++++--------- plugins/http_plugin/test/unit_tests.cpp | 64 +++--- tests/CMakeLists.txt | 4 +- tests/TestHarness/Cluster.py | 12 +- tests/TestHarness/TestHelper.py | 6 +- .../TransactionGeneratorsLauncher.py | 2 +- tests/TestHarness/WalletMgr.py | 9 +- tests/TestHarness/launcher.py | 4 +- tests/TestHarness/testUtils.py | 92 ++++++--- tests/nodeop_forked_chain_test.py | 4 +- tests/p2p_multiple_listen_test.py | 49 +++-- tests/p2p_no_blocks_test.py | 4 +- tests/p2p_no_listen_test.py | 2 +- tests/plugin_http_api_test.py | 8 +- tests/resource_monitor_plugin_test.py | 4 +- tests/ship_kill_client_test.py | 2 +- tests/ship_kv_delta_test.py | 2 +- tests/ship_reqs_across_svnn_test.py | 4 +- tests/ship_restart_test.py | 2 +- tests/ship_streamer_test.py | 4 +- tests/ship_test.py | 19 +- tests/test_port_shard.hpp | 135 +++++++++---- tests/trx_generator/CMakeLists.txt | 2 +- tests/trx_generator/trx_generator_tests.cpp | 3 +- 25 files changed, 371 insertions(+), 258 deletions(-) diff --git a/cmake/test-helpers.cmake b/cmake/test-helpers.cmake index fa21a38246..c1602709c6 100644 --- a/cmake/test-helpers.cmake +++ b/cmake/test-helpers.cmake @@ -1,5 +1,5 @@ set(SYSIO_TEST_PORT_OFFSET_START 100) -set(SYSIO_TEST_PORT_OFFSET_STRIDE 256) +set(SYSIO_TEST_PORT_OFFSET_STRIDE 192) function(next_test_port_offset out_var) get_property(next_offset GLOBAL PROPERTY SYSIO_NEXT_TEST_PORT_OFFSET) diff --git a/docs/port-sharding-test-plan.md b/docs/port-sharding-test-plan.md index dc7c60e444..2ad5b8fda2 100644 --- a/docs/port-sharding-test-plan.md +++ b/docs/port-sharding-test-plan.md @@ -1,15 +1,15 @@ -# Port Sharding Test Plan +# Port Sharding Test Design -This document describes the current port sharding scheme used by the Python integration tests. The goal is to let -tests that used to require serialized execution run concurrently without binding the same local listener port. +This document describes how the Python integration tests and the C++ test helper assign ports so +`nonparallelizable_tests` and `long_running_tests` can run concurrently without listener collisions. Port sharding has two layers: -1. CTest assigns each sharded test a unique `SYSIO_TEST_PORT_OFFSET`. -2. The test harness maps known hot listener ports into a compact, non-overlapping 256-port range for that offset. +1. CTest assigns each NP/LR test a unique `SYSIO_TEST_PORT_OFFSET`. +2. Test code asks for ports by logical category with `getPort(category, index)`. -When `SYSIO_TEST_PORT_OFFSET` is unset or `0`, the harness preserves the historical local ports such as `8888`, -`9876`, and `9899`. +When `SYSIO_TEST_PORT_OFFSET` is unset or `0`, the helpers return the historical local ports such as `8888`, `9876`, +and `9899`. This keeps direct manual test runs familiar. ## CTest Offset Allocation @@ -17,87 +17,92 @@ CTest offsets are assigned in `cmake/test-helpers.cmake` by one shared allocator ```cmake set(SYSIO_TEST_PORT_OFFSET_START 100) -set(SYSIO_TEST_PORT_OFFSET_STRIDE 256) +set(SYSIO_TEST_PORT_OFFSET_STRIDE 192) ``` -Both `add_np_test()` and `add_lr_test()` call the same allocator, so `nonparallelizable_tests` and -`long_running_tests` cannot receive duplicate offsets during the same CMake configure. +Both `add_np_test()` and `add_lr_test()` call this allocator. The allocator is global, not label-specific, so an NP +test and an LR test cannot receive the same offset during one CMake configure. The generated sequence is compact and deterministic: ```text -100, 356, 612, 868, 1124, ... +100, 292, 484, 676, 868, ... ``` -Each offset reserves one 256-port shard. The allocator is global instead of label-specific, which avoids the older -failure mode where an NP test and an LR test could both receive offset `1000`. - -## Harness Port Mapping - -All test-authored local listener ports should go through `Utils.shardPort(port)` or through a `Cluster` / -`WalletMgr` helper that already calls it. - -For nonzero offsets, the harness computes: +Each offset reserves one 192-port shard. For a nonzero offset, category slots are mapped from this base: ```text shard_base = 8888 + SYSIO_TEST_PORT_OFFSET ``` -Known hot ports are then placed into fixed slots inside the 256-port shard: +## Category Mapping + +New test listener ports must use the logical category API: -| Raw port or range | Use | Sharded port | -|---|---|---| -| `9899..9999` | wallet ports | `shard_base + 0..99` | -| `8788` | BIOS HTTP helper port | `shard_base + 100` | -| `7899` | SHiP / explicit service port | `shard_base + 150` | -| `8080` | state history endpoint default | `shard_base + 151` | -| `9011` | alternate explicit service port | `shard_base + 152` | -| `9776..9822` | alternate P2P/listener ports | `shard_base + 153..199` | -| `8888` | normal node HTTP base | `shard_base + 200` | -| `9876..9898` | normal node P2P base/range | `shard_base + 225..247` | +- Python: `Utils.getPort(Utils.PortStateHistory, index)` +- C++: `sysio::testing::get_port(sysio::testing::port_category::state_history, index)` -Ports that are not in the compact map fall back to `port + SYSIO_TEST_PORT_OFFSET`. That fallback preserves old -manual sharding behavior, but new listener ports should be added to the compact map when they are part of the NP/LR -concurrent test surface. +The old raw-port compatibility helpers `Utils.shardPort(port)` and `sysio::testing::shard_port(port)` were introduced +only for this port-sharding work and have been removed. New code should not add another raw-port fallback. Assign a +category instead, because categories make collisions visible at review time. -The C++ SHiP test clients use the same compact mapping through `tests/test_port_shard.hpp`. Keep that helper in sync -with `Utils.shardPort()` whenever the compact map changes. Otherwise Python may start `nodeop` on the compact -state-history endpoint while `ship_client` or `ship_streamer` still tries to connect to `8080 + offset`. +Current category slots are: -The performance harness also has non-port shared state. Its timestamped artifact directory includes the target TPS, -`SYSIO_TEST_PORT_OFFSET`, and PID so concurrent `performance_test_basic_*` runs do not scrape each other's -`trxGenLogs`. +| Category | Default unsharded port | Slot range | Capacity | Primary use | +|---|---:|---:|---:|---| +| `ship` | `7899` | `0` | `1` | explicit SHiP helper port | +| `state_history` | `8080` | `1` | `1` | state history websocket endpoint | +| `bios_http` | `8788` | `2` | `1` | BIOS HTTP endpoint | +| `node_http` | `8888` | `3..90` | `88` | node HTTP endpoints | +| `alternate_service` | `8976` | `91` | `1` | explicit alternate service listener | +| `plugin_http_peer` | `9009` | `92` | `1` | plugin HTTP peer endpoint | +| `plugin_http_local` | `9011` | `93` | `1` | plugin HTTP local endpoint | +| `alternate_p2p` | `9776` | `94..140` | `47` | alternate P2P/listener endpoints | +| `p2p` | `9876` | `141..163` | `23` | normal node P2P endpoints | +| `wallet` | `9899` | `164..168` | `5` | wallet/kiod endpoints | +| `transaction_only` | `9902` | `169..170` | `2` | transaction-only P2P endpoints | +| `ipv6_probe` | `9997` | `171..174` | `4` | IPv6 probe listeners | -## Why The Slots Do Not Collide +The current highest used slot is `174`, leaving slots `175..191` reserved for future categories without changing the +CTest stride. -Adjacent CTest shards differ by `256`. The compact mapping only uses slots `0..247` today, so no mapped hot listener -from one test can overlap the mapped hot listener ports from the next test. +## Why Categories Avoid Collisions -For example, with offsets `100` and `356`: +Adjacent CTest shards differ by `192`. The category allocator only uses slots `0..174`, so no mapped listener from one +test can overlap the mapped listener ports from the next test. -| Raw port | Offset `100` | Offset `356` | +For example, with offsets `100` and `292`: + +| Category/index | Offset `100` | Offset `292` | |---|---:|---:| -| wallet base `9899` | `8988` | `9244` | -| BIOS HTTP `8788` | `9088` | `9344` | -| SHiP `7899` | `9138` | `9394` | -| node HTTP `8888` | `9188` | `9444` | -| node P2P `9876` | `9213` | `9469` | +| `state_history` | `8989` | `9181` | +| `bios_http` | `8990` | `9182` | +| `node_http[0]` | `8991` | `9183` | +| `alternate_p2p[0]` | `9082` | `9274` | +| `p2p[0]` | `9129` | `9321` | +| `wallet[0]` | `9152` | `9344` | +| `transaction_only[0]` | `9157` | `9349` | -The first shard's compact hot range ends before the second shard starts. +The first shard's active category range ends before the next shard starts. -## Ephemeral Port Consideration +## Shared State Considerations -The allocator starts low and uses compact 256-port strides so hot listener ports stay below the OS ephemeral range for -as many tests as possible. On the current Linux runner, `/proc/sys/net/ipv4/ip_local_port_range` starts at `32768`. +Port sharding only solves listener-port conflicts. Some tests also need non-port shared state kept local to the test: -With the current combined NP/LR test list, the highest assigned offset observed during CTest metadata validation was -`22884`. The highest compact hot listener port is therefore: +- The performance harness includes target TPS, `SYSIO_TEST_PORT_OFFSET`, and PID in generated artifact paths so + concurrent performance tests do not scrape each other's `trxGenLogs`. +- `ship_test_unix` uses a short absolute Unix socket path under `/tmp`, keyed by the allocated state-history port. + This avoids the Linux `sockaddr_un` path length limit when the build directory or `TestLogs` path is long. +- Wallet-manager shutdown clears the tracked process so later tests do not inherit stale `kiod` state. -```text -8888 + 22884 + 255 = 32027 -``` +## Timeout Considerations -That stays below `32768`, leaving test listener ports out of the default ephemeral allocation range on that runner. +`nodeop_irreversible_mode_lr_test` is a 20-node scenario that validates 19 relaunch, replay, read-mode, and snapshot +combinations. Under high concurrent NP/LR execution it can exceed the default CTest timeout while still making +progress. The test therefore has an explicit `TIMEOUT 1500`. + +This timeout is not a port-sharding mechanism; it prevents a legitimate long-running scenario from being killed early +when the suite is intentionally run with high parallelism. ## Developer Usage @@ -108,23 +113,24 @@ cd build/codex-system-contracts SYSIO_TEST_PORT_OFFSET=100 python3 tests/nodeop_run_test.py ``` -When launching tests manually at the same time, choose offsets separated by at least `256`: +When launching tests manually at the same time, choose offsets separated by at least `192`: ```bash SYSIO_TEST_PORT_OFFSET=100 python3 tests/nodeop_run_test.py -SYSIO_TEST_PORT_OFFSET=356 python3 tests/http_plugin_test.py +SYSIO_TEST_PORT_OFFSET=292 python3 tests/http_plugin_test.py ``` New test endpoints should use one of these patterns: -- `Utils.shardPort()` for an explicit listener port. +- `Utils.getPort(Utils.PortStateHistory)` for a named Python listener category. +- `Utils.getPort(Utils.PortP2P, node_id)` for an indexed Python listener category. - `Cluster.getHttpEndpoint(node_id)` for node HTTP endpoints. - `Cluster.getNodeP2pEndpoint(node_id)` for node P2P endpoints. - `Cluster.getBiosP2pEndpoint()` for the BIOS P2P endpoint. -- `sysio::testing::shard_port()` in C++ test helpers or test binaries. +- `sysio::testing::get_port(port_category::state_history)` in C++ test helpers or test binaries. -Do not hardcode shifted port numbers in tests. Keep the raw historical port in the test and let the harness assign the -correct shard. +Do not hardcode shifted port numbers in tests. Keep the unsharded category default local and let the harness assign +the current test's shard. ## Validation Commands @@ -160,30 +166,22 @@ print("count", len(offsets)) print("duplicates", len(offsets) - len(set(offsets))) print("min_offset", min(offsets)) print("max_offset", max(offsets)) -print("max_compact_hot_port", 8888 + max(offsets) + 255) +print("max_hot_port", 8888 + max(offsets) + 191) PY ``` -Run a collision-sensitive long-running subset concurrently: +Run the full NP/LR set with high local concurrency: ```bash -ctest --test-dir build/codex-system-contracts -j3 -L long_running_tests \ - -R 'ship_streamer_test|ship_streamer_if_fetch_finality_data_test|auto_bp_gossip_peering_test' \ - --output-on-failure --timeout 1800 -``` - -Run the full NP/LR set with normal high concurrency: - -```bash -ctest --test-dir build/codex-system-contracts -j25 -L 'nonparallelizable_tests|long_running_tests' \ - --output-on-failure --timeout 1800 +ctest --test-dir build/codex-system-contracts -j30 -L 'nonparallelizable_tests|long_running_tests' \ + --output-on-failure ``` Stress all NP/LR tests at once to look for immediate listener collisions: ```bash ctest --test-dir build/codex-system-contracts -j90 -L 'nonparallelizable_tests|long_running_tests' \ - --output-on-failure --timeout 1800 + --output-on-failure ``` Then scan the log for collision signatures: @@ -195,37 +193,29 @@ rg 'Address already in use|bind|Failed to bind|Failed to find free port|port .*n ## Current Validation Results -The current combined NP/LR metadata assigns 90 offsets with no duplicates. The observed offset range is: +The current combined NP/LR metadata assigns 105 offsets with no duplicates: ```text min_offset = 100 -max_offset = 22884 -max_compact_hot_port = 32027 +max_offset = 20068 +max_hot_port = 29147 ``` -`-j25` full NP/LR validation passed: +Targeted validation after the latest fixes: ```text -100% tests passed, 0 tests failed out of 90 -Total Test time (real) = 1047.14 sec +p2p_multiple_listen_test: passed +nodeop_irreversible_mode_lr_test: passed +ship_test_unix: passed ``` -`-j90` stress validation did not find port-collision signatures. It completed with 86/90 tests passing. The failures -were overload/timing/resource-saturation failures, not listener collisions: - -| Test | Observed failure | -|---|---| -| `cli_test` | `node doesn't appear to be running` after node startup under full-suite load | -| `separate_prod_fin_test` | `tx_cpu_usage_exceeded` while publishing `sysio.system` | -| `terminate-scenarios-test-resync` | `Block production handover failed` | -| `nodeop_read_terminate_at_block_lr_test` | block progress assertion: end block was not greater than terminate block | - -The collision-sensitive SHiP tests and performance harness tests passed under `-j90`, which is the strongest current -evidence that port sharding itself is working. +A full `-j30` NP/LR run after fixing `p2p_multiple_listen_test` and `nodeop_irreversible_mode_lr_test` showed both +original failures passing. It then exposed `ship_test_unix` failing with `File name too long` on the Unix socket path. +That was fixed by moving the Unix socket to the short `/tmp/sysio-ship-.sock` path and validated with a targeted +`ship_test_unix` run. ## Known Non-Port Limits Port sharding removes listener collisions; it does not make every NP/LR test safe under unbounded machine load. -At `-j90`, many tests bootstrap chains, publish large contracts, run transaction generators, and start many `nodeop` -processes at once. The observed failures are consistent with CPU and timing pressure. Treat `-j90` as a collision -stress probe, not as the expected CI concurrency level. +At very high parallelism, many tests bootstrap chains, publish large contracts, run transaction generators, and start +many `nodeop` processes at once. Treat `-j90` as a collision stress probe, not as the expected CI concurrency level. diff --git a/plugins/http_plugin/test/unit_tests.cpp b/plugins/http_plugin/test/unit_tests.cpp index 0294c3d986..8fb35dbbda 100644 --- a/plugins/http_plugin/test/unit_tests.cpp +++ b/plugins/http_plugin/test/unit_tests.cpp @@ -34,21 +34,21 @@ namespace net = boost::asio; // from using tcp = net::ip::tcp; // from namespace { -constexpr uint16_t default_http_port = 8888; -constexpr uint16_t category_rw_port = 8889; -constexpr uint16_t category_ro_port = 8890; -constexpr uint16_t bytes_in_flight_port = 8891; -constexpr uint16_t requests_in_flight_port = 8892; -constexpr uint16_t ipv6_probe_port = 9999; - -/** Return the string form of a base port after applying this test's shard. */ -std::string test_port(uint16_t port) { - return std::to_string(sysio::testing::shard_port(port)); +constexpr uint32_t default_http_index = 0; +constexpr uint32_t category_rw_index = 1; +constexpr uint32_t category_ro_index = 2; +constexpr uint32_t bytes_in_flight_index = 3; +constexpr uint32_t requests_in_flight_index = 4; +constexpr uint32_t ipv6_probe_index = 2; + +/** Return the string form of a node HTTP port from this test's shard. */ +std::string test_http_port(uint32_t index) { + return std::to_string(sysio::testing::get_port(sysio::testing::port_category::node_http, index)); } /** Return host:port with this test's port shard applied. */ -std::string test_endpoint(const std::string& host, uint16_t port) { - return host + ":" + test_port(port); +std::string test_http_endpoint(const std::string& host, uint32_t index) { + return host + ":" + test_http_port(index); } } // namespace @@ -321,8 +321,8 @@ struct http_plugin_test_fixture { // ------------------------------------------------------------------------- BOOST_FIXTURE_TEST_CASE(http_plugin_unit_tests, http_plugin_test_fixture) { - const uint16_t default_port = sysio::testing::shard_port(default_http_port); - const std::string port = test_port(default_http_port); + const uint16_t default_port = sysio::testing::get_port(sysio::testing::port_category::node_http); + const std::string port = test_http_port(default_http_index); const char* host = "127.0.0.1"; http_plugin::set_defaults({.default_unix_socket_path = "", .default_http_port = default_port, .server_header = "/"}); @@ -420,13 +420,13 @@ class app_log { BOOST_AUTO_TEST_CASE(invalid_category_addresses) { const char* test_name = bu::framework::current_test_case().p_name->c_str(); - const std::string localhost_rw = test_endpoint("localhost", category_rw_port); - const std::string loopback_rw = test_endpoint("127.0.0.1", category_rw_port); + const std::string localhost_rw = test_http_endpoint("localhost", category_rw_index); + const std::string loopback_rw = test_http_endpoint("127.0.0.1", category_rw_index); const std::string chain_ro_localhost = "chain_ro," + localhost_rw; const std::string chain_ro_loopback = "chain_ro," + loopback_rw; const std::string chain_rw_localhost = "chain_rw," + localhost_rw; const std::string node_localhost = "node," + localhost_rw; - const std::string unable_to_listen_msg = "unable to listen to port " + test_port(category_rw_port); + const std::string unable_to_listen_msg = "unable to listen to port " + test_http_port(category_rw_index); BOOST_TEST(app_log({test_name, "--plugin=sysio::http_plugin", "--http-server-address", "http-category-address", "--http-category-address", chain_ro_localhost.c_str()}) @@ -495,11 +495,11 @@ struct http_response_for { BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { fc::temp_directory dir; auto data_dir = dir.path() / "data"; - const std::string ro_port = test_port(category_ro_port); - const std::string rw_port = test_port(category_rw_port); - const std::string ro_loopback = test_endpoint("127.0.0.1", category_ro_port); - const std::string ro_localhost = test_endpoint("localhost", category_ro_port); - const std::string rw_loopback = test_endpoint("127.0.0.1", category_rw_port); + const std::string ro_port = test_http_port(category_ro_index); + const std::string rw_port = test_http_port(category_rw_index); + const std::string ro_loopback = test_http_endpoint("127.0.0.1", category_ro_index); + const std::string ro_localhost = test_http_endpoint("localhost", category_ro_index); + const std::string rw_loopback = test_http_endpoint("127.0.0.1", category_rw_index); const std::string rw_any = ":" + rw_port; const std::string chain_ro = "chain_ro," + ro_loopback; const std::string chain_rw = "chain_rw," + rw_any; @@ -570,7 +570,9 @@ BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { bool ip_v6_enabled = [] { try { net::io_context ioc; - tcp::socket s(ioc, tcp::endpoint{net::ip::make_address("::1"), sysio::testing::shard_port(ipv6_probe_port)}); + tcp::socket s(ioc, tcp::endpoint{net::ip::make_address("::1"), + sysio::testing::get_port(sysio::testing::port_category::ipv6_probe, + ipv6_probe_index)}); return true; } catch (...) { return false; @@ -610,10 +612,10 @@ bool on_loopback(std::initializer_list args){ } BOOST_AUTO_TEST_CASE(test_on_loopback) { - const std::string loopback_default = test_endpoint("127.0.0.1", default_http_port); - const std::string localhost_default = test_endpoint("localhost", default_http_port); - const std::string any_default = ":" + test_port(default_http_port); - const std::string external_default = test_endpoint("example.com", default_http_port); + const std::string loopback_default = test_http_endpoint("127.0.0.1", default_http_index); + const std::string localhost_default = test_http_endpoint("localhost", default_http_index); + const std::string any_default = ":" + test_http_port(default_http_index); + const std::string external_default = test_http_endpoint("example.com", default_http_index); BOOST_CHECK(on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", "", "--unix-socket-path=a"})); BOOST_CHECK(on_loopback({"test", "--plugin=sysio::http_plugin", "--http-server-address", loopback_default.c_str()})); @@ -623,9 +625,9 @@ BOOST_AUTO_TEST_CASE(test_on_loopback) { } BOOST_FIXTURE_TEST_CASE(bytes_in_flight, http_plugin_test_fixture) { - const std::string endpoint = test_endpoint("127.0.0.1", bytes_in_flight_port); + const std::string endpoint = test_http_endpoint("127.0.0.1", bytes_in_flight_index); const std::string server_address = "--http-server-address=" + endpoint; - const std::string port = test_port(bytes_in_flight_port); + const std::string port = test_http_port(bytes_in_flight_index); http_plugin* http_plugin = init({"--plugin=sysio::http_plugin", server_address.c_str(), @@ -725,9 +727,9 @@ BOOST_FIXTURE_TEST_CASE(bytes_in_flight, http_plugin_test_fixture) { } BOOST_FIXTURE_TEST_CASE(requests_in_flight, http_plugin_test_fixture) { - const std::string endpoint = test_endpoint("127.0.0.1", requests_in_flight_port); + const std::string endpoint = test_http_endpoint("127.0.0.1", requests_in_flight_index); const std::string server_address = "--http-server-address=" + endpoint; - const std::string port = test_port(requests_in_flight_port); + const std::string port = test_http_port(requests_in_flight_index); http_plugin* http_plugin = init({"--plugin=sysio::http_plugin", server_address.c_str(), diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bac7bca3dd..ddf36670f3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -224,7 +224,9 @@ add_np_test(NAME nodeop_contrl_c_test COMMAND tests/nodeop_contrl_c_test.py -v) add_lr_test(NAME nodeop_under_min_avail_ram_lr_test COMMAND tests/nodeop_under_min_avail_ram.py -v) -add_lr_test(NAME nodeop_irreversible_mode_lr_test COMMAND tests/nodeop_irreversible_mode_test.py -v) +# This 20-node scenario validates 19 relaunch/replay/snapshot combinations and can exceed the default +# CTest timeout when the long-running suite is intentionally run with high parallelism. +add_lr_test(NAME nodeop_irreversible_mode_lr_test COMMAND tests/nodeop_irreversible_mode_test.py -v TIMEOUT 1500) add_lr_test(NAME nodeop_read_terminate_at_block_lr_test COMMAND tests/nodeop_read_terminate_at_block_test.py -v) diff --git a/tests/TestHarness/Cluster.py b/tests/TestHarness/Cluster.py index d4643416ec..574dce72fa 100644 --- a/tests/TestHarness/Cluster.py +++ b/tests/TestHarness/Cluster.py @@ -58,7 +58,7 @@ class Cluster(object): __WalletName="MyWallet" __localHost="localhost" __BiosHost="localhost" - __BiosPort=Utils.shardPort(8788) + __BiosPort=Utils.getPort(Utils.PortBiosHttp) __LauncherCmdArr=[] __bootlog="wire_sysio-ignition-wd/bootlog.txt" __localLaunchPortCheckAttempts=90 @@ -90,10 +90,10 @@ def __init__(self, localCluster=True, host="localhost", port=None, walletHost="l self.wallet=None self.walletMgr=None self.host=host - self.port=Utils.shardPort(8888) if port is None else port - self.p2pBasePort=Utils.shardPort(9876) + self.port=Utils.getPort(Utils.PortNodeHttp) if port is None else port + self.p2pBasePort=Utils.getPort(Utils.PortP2P) self.walletHost=walletHost - self.walletPort=Utils.shardPort(9899) if walletPort is None else walletPort + self.walletPort=Utils.getPort(Utils.PortWallet) if walletPort is None else walletPort self.staging=staging self.loggingLevel=loggingLevel self.loggingLevelDict=loggingLevelDict @@ -192,9 +192,9 @@ def _shutdownLaunchFailure(self): def _portsForLocalLaunch(self, totalNodes): """Return the local HTTP and P2P ports reserved by a cluster launch.""" - ports = set(range(self.port, self.port + totalNodes + 1)) + ports = set(range(self.port, self.port + totalNodes)) ports.add(self.port - 100) - ports.update(range(self.p2pBasePort, self.p2pBasePort + totalNodes + 1)) + ports.update(range(self.p2pBasePort, self.p2pBasePort + totalNodes)) ports.add(self.p2pBasePort - 100) return ports diff --git a/tests/TestHarness/TestHelper.py b/tests/TestHarness/TestHelper.py index 8a3c2c778b..e061b2c47e 100644 --- a/tests/TestHarness/TestHelper.py +++ b/tests/TestHarness/TestHelper.py @@ -37,8 +37,8 @@ def add_bool(self, flag, help, action='store_true'): # pylint: disable=too-many-instance-attributes class TestHelper(object): LOCAL_HOST="localhost" - DEFAULT_PORT=Utils.shardPort(8888) - DEFAULT_WALLET_PORT=Utils.shardPort(9899) + DEFAULT_PORT=Utils.getPort(Utils.PortNodeHttp) + DEFAULT_WALLET_PORT=Utils.getPort(Utils.PortWallet) @staticmethod # pylint: disable=too-many-branches @@ -195,3 +195,5 @@ def reportProductionAnalysis(thresholdMs): walletMgr.testFailed = not testSuccessful cluster.shutdown() + if walletMgr: + walletMgr.shutdown() diff --git a/tests/TestHarness/TransactionGeneratorsLauncher.py b/tests/TestHarness/TransactionGeneratorsLauncher.py index 8b282d5108..2e6f811931 100644 --- a/tests/TestHarness/TransactionGeneratorsLauncher.py +++ b/tests/TestHarness/TransactionGeneratorsLauncher.py @@ -115,7 +115,7 @@ def parseArgs(): parser.add_argument("actions_auths", type=str, help="The json actions auth file or json actions auths description string to use, containting authAcctName to activePrivateKey pairs.") parser.add_argument("connection_pair_list", type=str, help="Comma separated list of endpoint:port combinations to send transactions to", - default=f"localhost:{Utils.shardPort(9876)}") + default=f"localhost:{Utils.getPort(Utils.PortP2P)}") parser.add_argument("endpoint_mode", type=str, help="Endpoint mode (\"p2p\", \"http\"). \ In \"p2p\" mode transactions will be directed to the p2p endpoint on a producer node. \ In \"http\" mode transactions will be directed to the http endpoint on an api node.", diff --git a/tests/TestHarness/WalletMgr.py b/tests/TestHarness/WalletMgr.py index e16f38ce56..5ed98608c7 100644 --- a/tests/TestHarness/WalletMgr.py +++ b/tests/TestHarness/WalletMgr.py @@ -24,9 +24,9 @@ def __init__(self, walletd, nodeopPort=None, nodeopHost="localhost", port=None, """Create a wallet manager using sharded default ports when callers omit explicit ports.""" atexit.register(self.shutdown) self.walletd=walletd - self.nodeopPort=Utils.shardPort(8888) if nodeopPort is None else nodeopPort + self.nodeopPort=Utils.getPort(Utils.PortNodeHttp) if nodeopPort is None else nodeopPort self.nodeopHost=nodeopHost - self.port=Utils.shardPort(9899) if port is None else port + self.port=Utils.getPort(Utils.PortWallet) if port is None else port self.usesDefaultWalletPort=port is None self.host=host self.keepRunning=keepRunning @@ -53,7 +53,7 @@ def isLocal(self): def findAvailablePort(self): """Find an available wallet port within this test's shard.""" - shardLimit=Utils.shardPort(WalletMgr.__MaxPort) if self.usesDefaultWalletPort else 65535 + shardLimit=Utils.getPort(Utils.PortWallet, Utils.WalletPortCount - 1) if self.usesDefaultWalletPort else 65535 for i in range(WalletMgr.__MaxPort): port=self.port+i # pyright: ignore[reportOptionalOperand] if Utils.getTestPortOffset() > 0 and port > shardLimit: @@ -323,9 +323,12 @@ def shutdown(self): Utils.Print(f"Shutting down wallet manager process {self.walletPid}") self.popenProc.send_signal(signal.SIGTERM) self.popenProc.wait() + self.popenProc=None + self.walletPid=None elif self.walletPid: Utils.Print("Killing wallet manager process %d" % (self.walletPid)) os.kill(self.walletPid, signal.SIGKILL) + self.walletPid=None self.cleanup() def cleanup(self): diff --git a/tests/TestHarness/launcher.py b/tests/TestHarness/launcher.py index a144fe677f..7f44df2917 100644 --- a/tests/TestHarness/launcher.py +++ b/tests/TestHarness/launcher.py @@ -50,8 +50,8 @@ class nodeDefinition: data_dir_name: str = field(init=False) p2p_port: int = 0 http_port: int = 0 - base_p2p_port: ClassVar[int] = Utils.shardPort(9876) - base_http_port: ClassVar[int] = Utils.shardPort(8888) + base_p2p_port: ClassVar[int] = Utils.getPort(Utils.PortP2P) + base_http_port: ClassVar[int] = Utils.getPort(Utils.PortNodeHttp) host_name: str = 'localhost' public_name: str = 'localhost' listen_addr: str = '0.0.0.0' diff --git a/tests/TestHarness/testUtils.py b/tests/TestHarness/testUtils.py index d060bc4501..918c359d4d 100644 --- a/tests/TestHarness/testUtils.py +++ b/tests/TestHarness/testUtils.py @@ -91,6 +91,19 @@ class Utils: TimeFmt='%Y-%m-%dT%H:%M:%S.%f' TestPortOffsetEnvVar="SYSIO_TEST_PORT_OFFSET" + PortShip="ship" + PortStateHistory="state_history" + PortBiosHttp="bios_http" + PortNodeHttp="node_http" + PortAlternateService="alternate_service" + PortPluginHttpPeer="plugin_http_peer" + PortPluginHttpLocal="plugin_http_local" + PortAlternateP2P="alternate_p2p" + PortP2P="p2p" + PortWallet="wallet" + PortTransactionOnly="transaction_only" + PortIpv6Probe="ipv6_probe" + WalletPortCount=5 _testPortOffset=None # lock to serialize writes to subprocess_results.log across threads _check_output_lock = threading.Lock() @@ -114,41 +127,66 @@ def getTestPortOffset(): return offset @staticmethod - def shardPort(port): - """Apply the configured test port offset to a raw base port. + def getPort(port_category, index=0): + """Return a deterministic port from this test's compact port shard. - Known local test ports are compacted into a single 256-port shard so - adjacent CTest port shards cannot overlap HTTP, P2P, SHiP, or wallet - listeners from different tests. + port_category names the listener class and index selects a listener + within that class. CTest assigns each test a unique shard offset, and + this allocator keeps all known listener classes inside one bounded + 192-port shard below the ephemeral range. """ - assert(isinstance(port, int)) - offset=Utils.getTestPortOffset() - if offset == 0: - return port - - shardBase=8888 + offset - compactPortMap={ - 8788: shardBase + 100, - 7899: shardBase + 150, - 8080: shardBase + 151, - 9011: shardBase + 152, - 8888: shardBase + 200, + assert(isinstance(port_category, str)) + assert(isinstance(index, int)) + if index < 0: + raise RuntimeError(f"Port index must be non-negative, got {index}") + + slotRanges={ + Utils.PortShip: (0, 1), + Utils.PortStateHistory: (1, 1), + Utils.PortBiosHttp: (2, 1), + Utils.PortNodeHttp: (3, 88), + Utils.PortAlternateService: (91, 1), + Utils.PortPluginHttpPeer: (92, 1), + Utils.PortPluginHttpLocal: (93, 1), + Utils.PortAlternateP2P: (94, 47), + Utils.PortP2P: (141, 23), + Utils.PortWallet: (164, Utils.WalletPortCount), + Utils.PortTransactionOnly: (169, 2), + Utils.PortIpv6Probe: (171, 4), } - if port in compactPortMap: - shiftedPort=compactPortMap[port] - elif 9776 <= port < 9823: - shiftedPort=shardBase + 153 + (port - 9776) - elif 9876 <= port < 9899: - shiftedPort=shardBase + 225 + (port - 9876) - elif 9899 <= port <= 9999: - shiftedPort=shardBase + min(port - 9899, 99) + if port_category not in slotRanges: + raise RuntimeError(f"Unknown port category '{port_category}'") + + slotStart, slotCount=slotRanges[port_category] + if index >= slotCount: + raise RuntimeError( + f"Port index {index} is outside category '{port_category}' capacity {slotCount}") + + offset=Utils.getTestPortOffset() + if offset == 0: + defaultPorts={ + Utils.PortShip: 7899, + Utils.PortStateHistory: 8080, + Utils.PortBiosHttp: 8788, + Utils.PortNodeHttp: 8888, + Utils.PortAlternateService: 8976, + Utils.PortPluginHttpPeer: 9009, + Utils.PortPluginHttpLocal: 9011, + Utils.PortAlternateP2P: 9776, + Utils.PortP2P: 9876, + Utils.PortWallet: 9899, + Utils.PortTransactionOnly: 9902, + Utils.PortIpv6Probe: 9997, + } + shiftedPort=defaultPorts[port_category] + index else: - shiftedPort=port + offset + shiftedPort=8888 + offset + slotStart + index if shiftedPort < 1 or shiftedPort > 65535: raise RuntimeError( - f"Port {port} shifted by {Utils.TestPortOffsetEnvVar}={Utils.getTestPortOffset()} " + f"Port category '{port_category}' index {index} shifted by " + f"{Utils.TestPortOffsetEnvVar}={Utils.getTestPortOffset()} " f"produces invalid port {shiftedPort}") return shiftedPort diff --git a/tests/nodeop_forked_chain_test.py b/tests/nodeop_forked_chain_test.py index c2a3fe195f..3a9bcf7054 100755 --- a/tests/nodeop_forked_chain_test.py +++ b/tests/nodeop_forked_chain_test.py @@ -157,7 +157,7 @@ def getMinHeadAndLib(prodNodes): shipNodeNum = 0 specificExtraNodeopArgs[shipNodeNum]=( "--plugin sysio::state_history_plugin " - f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)}" + f"--state-history-endpoint 127.0.0.1:{Utils.getPort(Utils.PortStateHistory)}" ) # producer nodes will be mapped to 0 through totalProducerNodes-1, so the number totalProducerNodes will be the non-producing node @@ -289,7 +289,7 @@ def getBlock(self, blockNum): shipClient = "tests/ship_streamer" cmd = ( - f"{shipClient} --socket-address 127.0.0.1:{Utils.shardPort(8080)} " + f"{shipClient} --socket-address 127.0.0.1:{Utils.getPort(Utils.PortStateHistory)} " f"--start-block-num {start_block_num} --end-block-num {end_block_num} " "--fetch-block --fetch-traces --fetch-deltas" ) diff --git a/tests/p2p_multiple_listen_test.py b/tests/p2p_multiple_listen_test.py index 58040c2171..803e5e2804 100755 --- a/tests/p2p_multiple_listen_test.py +++ b/tests/p2p_multiple_listen_test.py @@ -13,6 +13,8 @@ Print=Utils.Print errorExit=Utils.errorExit +advertisedP2pEndpoint0 = 'ext-ip0:20000' +advertisedP2pEndpoint1 = 'ext-ip1:20001' args=TestHelper.parse_args({"-p","-n","-d","--keep-logs" ,"--activate-if","--dump-error-details","-v" @@ -30,6 +32,19 @@ cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) walletMgr=WalletMgr(True) +def getOpenPeerAddress(node): + """Return the single open peer address advertised to the supplied node by node 00.""" + connections = node.processUrllibRequest('net', 'connections') + openPeerAddresses = [] + for conn in connections['payload']: + if conn['is_socket_open']: + connectedAgent = conn['last_handshake']['agent'] + assert connectedAgent == 'node-00', f"Connected node identified as '{connectedAgent}' instead of node-00" + openPeerAddresses.append(conn['last_handshake']['p2p_address'].split()[0]) + + assert len(openPeerAddresses) == 1, f'Node {node.nodeId} is expected to have exactly one open socket' + return openPeerAddresses[0] + try: TestHelper.printSystemInfo("BEGIN") @@ -38,12 +53,12 @@ Print(f'producing nodes: {pnodes}, delay between nodes launch: {delay} second{"s" if delay != 1 else ""}') Print("Stand up cluster") - alternateListenEndpoint = f"0.0.0.0:{Utils.shardPort(9779)}" - alternatePeerEndpoint = f"localhost:{Utils.shardPort(9779)}" + alternateListenEndpoint = f"0.0.0.0:{Utils.getPort(Utils.PortAlternateP2P, 3)}" + alternatePeerEndpoint = f"localhost:{Utils.getPort(Utils.PortAlternateP2P, 3)}" specificArgs = { '0': f'--agent-name node-00 --p2p-listen-endpoint 0.0.0.0:{cluster.getNodeP2pPort(0)} ' - f'--p2p-listen-endpoint {alternateListenEndpoint} --p2p-server-address ext-ip0:20000 ' - f'--p2p-server-address ext-ip1:20001 --plugin sysio::net_api_plugin', + f'--p2p-listen-endpoint {alternateListenEndpoint} --p2p-server-address {advertisedP2pEndpoint0} ' + f'--p2p-server-address {advertisedP2pEndpoint1} --plugin sysio::net_api_plugin', '2': f'--agent-name node-02 --p2p-peer-address {alternatePeerEndpoint} --plugin sysio::net_api_plugin', '4': f'--agent-name node-04 --p2p-peer-address {cluster.getNodeP2pEndpoint(0)} --plugin sysio::net_api_plugin', } @@ -84,25 +99,15 @@ assert conn['last_handshake']['p2p_address'].split()[0] == expectedEndpoint, f"Connected node is listening on '{conn['last_handshake']['p2p_address'].split()[0]}' instead of {expectedEndpoint}" assert open_socket_count == 2, 'Node 0 is expected to have exactly two open sockets' - connections = cluster.nodes[2].processUrllibRequest('net', 'connections') - open_socket_count = 0 - for conn in connections['payload']: - if conn['is_socket_open']: - open_socket_count += 1 - assert conn['last_handshake']['agent'] == 'node-00', f"Connected node identified as '{conn['last_handshake']['agent']}' instead of node-00" - # Server addresses are paired positionally with listen endpoints: - # 9876 -> ext-ip0:20000, 9779 -> ext-ip1:20001. - assert conn['last_handshake']['p2p_address'].split()[0] == 'ext-ip1:20001', f"Connected node is advertising '{conn['last_handshake']['p2p_address'].split()[0]}' instead of ext-ip1:20001" - assert open_socket_count == 1, 'Node 2 is expected to have exactly one open socket' + # Server addresses are paired positionally with listen endpoints: + # default listen endpoint -> ext-ip0:20000, alternate listen endpoint -> ext-ip1:20001. + node2AdvertisedAddress = getOpenPeerAddress(cluster.nodes[2]) + assert node2AdvertisedAddress == advertisedP2pEndpoint1, \ + f"Connected node is advertising '{node2AdvertisedAddress}' instead of {advertisedP2pEndpoint1}" - connections = cluster.nodes[4].processUrllibRequest('net', 'connections') - open_socket_count = 0 - for conn in connections['payload']: - if conn['is_socket_open']: - open_socket_count += 1 - assert conn['last_handshake']['agent'] == 'node-00', f"Connected node identified as '{conn['last_handshake']['agent']}' instead of node-00" - assert conn['last_handshake']['p2p_address'].split()[0] == 'ext-ip0:20000', f"Connected node is advertising '{conn['last_handshake']['p2p_address'].split()[0]}' instead of ext-ip0:20000" - assert open_socket_count == 1, 'Node 4 is expected to have exactly one open socket' + node4AdvertisedAddress = getOpenPeerAddress(cluster.nodes[4]) + assert node4AdvertisedAddress == advertisedP2pEndpoint0, \ + f"Connected node is advertising '{node4AdvertisedAddress}' instead of {advertisedP2pEndpoint0}" testSuccessful=True finally: diff --git a/tests/p2p_no_blocks_test.py b/tests/p2p_no_blocks_test.py index 99ece671ef..30359a226e 100755 --- a/tests/p2p_no_blocks_test.py +++ b/tests/p2p_no_blocks_test.py @@ -56,8 +56,8 @@ # 02 & 03 are connected to the bios node to get blocks until bios node is killed. # specificExtraNodeopArgs = {} - trxOnlyPort02 = Utils.shardPort(9902) - trxOnlyPort03 = Utils.shardPort(9903) + trxOnlyPort02 = Utils.getPort(Utils.PortTransactionOnly) + trxOnlyPort03 = Utils.getPort(Utils.PortTransactionOnly, 1) # nonProdNode01 will connect normally but will not send blocks because 02 & 03 have specified :trx only specificExtraNodeopArgs[1] = f'--p2p-peer-address localhost:{trxOnlyPort02} --p2p-peer-address localhost:{trxOnlyPort03} ' # add a trx only listen endpoint to noBlocks02 & noBlocks03 diff --git a/tests/p2p_no_listen_test.py b/tests/p2p_no_listen_test.py index ad200aaf9b..626ade37a8 100755 --- a/tests/p2p_no_listen_test.py +++ b/tests/p2p_no_listen_test.py @@ -54,7 +54,7 @@ node.waitForBlock(5) s = socket.socket() - p2pPort = Utils.shardPort(9876) + p2pPort = Utils.getPort(Utils.PortP2P) err = s.connect_ex(('localhost', p2pPort)) assert err == errno.ECONNREFUSED, f'Connection to port {p2pPort} must be refused' diff --git a/tests/plugin_http_api_test.py b/tests/plugin_http_api_test.py index 7bec666b6a..5ed8c04727 100755 --- a/tests/plugin_http_api_test.py +++ b/tests/plugin_http_api_test.py @@ -53,7 +53,7 @@ class PluginHttpTest(unittest.TestCase): config_dir = Path(Utils.getNodeConfigDir(node_id)) empty_content_dict = {} http_post_invalid_param = '{invalid}' - p2p_peer_endpoint = f"localhost:{Utils.shardPort(9011)}" + p2p_peer_endpoint = f"localhost:{Utils.getPort(Utils.PortPluginHttpLocal)}" SYSIO_ACCT_PRIVATE_DEFAULT_KEY = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" SYSIO_ACCT_PUBLIC_DEFAULT_KEY = "SYS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" @@ -89,7 +89,7 @@ def startEnv(self) : self.createDataDir(self) self.createConfigDir(self) self.kiod.launch() - p2pEndpoint = f"{TestHelper.LOCAL_HOST}:{Utils.shardPort(9876)}" + p2pEndpoint = f"{TestHelper.LOCAL_HOST}:{Utils.getPort(Utils.PortP2P)}" httpServerAddressArg = "" if category_config.ports else ( f"--http-server-address {TestHelper.LOCAL_HOST}:{TestHelper.DEFAULT_PORT} ") plugin_names = ["trace_api_plugin", "test_control_api_plugin", "test_control_plugin", "net_plugin", @@ -102,7 +102,7 @@ def startEnv(self) : "%s--p2p-listen-endpoint 0.0.0.0:%d --p2p-server-address %s " "--p2p-peer-address %s --resource-monitor-not-shutdown-on-threshold-exceeded ") % ( self.data_dir, self.config_dir, self.data_dir, "\'*\'", "false", - httpServerAddressArg, Utils.shardPort(9876), p2pEndpoint, self.p2p_peer_endpoint) + httpServerAddressArg, Utils.getPort(Utils.PortP2P), p2pEndpoint, self.p2p_peer_endpoint) nodeop_flags += category_config.nodeopArgs() start_nodeop_cmd = ("%s -e -p sysio %s %s ") % (Utils.SysServerPath, nodeop_plugins, nodeop_flags) @@ -807,7 +807,7 @@ def test_NetApi(self) : ret_json = self.nodeop.processUrllibRequest(resource, command, payload, endpoint=endpoint) self.assertEqual(ret_json["code"], 201) self.assertEqual(ret_json["payload"], 'invalid peer address') - payload = f"localhost:{Utils.shardPort(9877)}" + payload = f"localhost:{Utils.getPort(Utils.PortP2P, 1)}" ret_str = self.nodeop.processUrllibRequest(resource, command, payload, returnType=ReturnType.raw, endpoint=endpoint).decode('ascii') self.assertEqual("\"added connection\"", ret_str) diff --git a/tests/resource_monitor_plugin_test.py b/tests/resource_monitor_plugin_test.py index affdb06581..4509106e73 100755 --- a/tests/resource_monitor_plugin_test.py +++ b/tests/resource_monitor_plugin_test.py @@ -178,9 +178,9 @@ def testAll(): testCommon("Resmon not enabled: no arguments", "", ["interval set to 2", "threshold set to 90", "Shutdown flag when threshold exceeded set to true", "snapshots's file system to be monitored", "blocks's file system to be monitored", "state's file system to be monitored"]) # default arguments with registered directories - testCommon("Resmon not enabled: Producer, Chain, State History and Trace Api", f"--plugin sysio::state_history_plugin --state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} --state-history-dir=/tmp/state-history --disable-replay-opts --plugin sysio::trace_api_plugin --trace-dir=/tmp/trace --trace-no-abis", ["interval set to 2", "threshold set to 90", "Shutdown flag when threshold exceeded set to true", "snapshots's file system to be monitored", "blocks's file system to be monitored", "state's file system to be monitored", "state-history's file system to be monitored", "trace's file system to be monitored"]) + testCommon("Resmon not enabled: Producer, Chain, State History and Trace Api", f"--plugin sysio::state_history_plugin --state-history-endpoint 127.0.0.1:{Utils.getPort(Utils.PortStateHistory)} --state-history-dir=/tmp/state-history --disable-replay-opts --plugin sysio::trace_api_plugin --trace-dir=/tmp/trace --trace-no-abis", ["interval set to 2", "threshold set to 90", "Shutdown flag when threshold exceeded set to true", "snapshots's file system to be monitored", "blocks's file system to be monitored", "state's file system to be monitored", "state-history's file system to be monitored", "trace's file system to be monitored"]) - testCommon("Resmon enabled: Producer, Chain, State History and Trace Api", f"--plugin sysio::resource_monitor_plugin --plugin sysio::state_history_plugin --state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} --state-history-dir=/tmp/state-history --disable-replay-opts --plugin sysio::trace_api_plugin --trace-dir=/tmp/trace --trace-no-abis --resource-monitor-space-threshold=80 --resource-monitor-interval-seconds=3", ["snapshots's file system to be monitored", "blocks's file system to be monitored", "state's file system to be monitored", "state-history's file system to be monitored", "trace's file system to be monitored", "threshold set to 80", "interval set to 3", "Shutdown flag when threshold exceeded set to true"]) + testCommon("Resmon enabled: Producer, Chain, State History and Trace Api", f"--plugin sysio::resource_monitor_plugin --plugin sysio::state_history_plugin --state-history-endpoint 127.0.0.1:{Utils.getPort(Utils.PortStateHistory)} --state-history-dir=/tmp/state-history --disable-replay-opts --plugin sysio::trace_api_plugin --trace-dir=/tmp/trace --trace-no-abis --resource-monitor-space-threshold=80 --resource-monitor-interval-seconds=3", ["snapshots's file system to be monitored", "blocks's file system to be monitored", "state's file system to be monitored", "state-history's file system to be monitored", "trace's file system to be monitored", "threshold set to 80", "interval set to 3", "Shutdown flag when threshold exceeded set to true"]) # Only test minimum warning threshold (i.e. 6) to trigger warning as much as possible testInterval("Resmon enabled: set warning interval", diff --git a/tests/ship_kill_client_test.py b/tests/ship_kill_client_test.py index d08b65c605..af12cd576a 100755 --- a/tests/ship_kill_client_test.py +++ b/tests/ship_kill_client_test.py @@ -53,7 +53,7 @@ "--plugin sysio::state_history_plugin " "--trace-history --chain-state-history --finality-data-history " "--state-history-stride 200 " - f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} " + f"--state-history-endpoint 127.0.0.1:{Utils.getPort(Utils.PortStateHistory)} " "--plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin " ) diff --git a/tests/ship_kv_delta_test.py b/tests/ship_kv_delta_test.py index fd1488dc3f..cea56c3d9a 100755 --- a/tests/ship_kv_delta_test.py +++ b/tests/ship_kv_delta_test.py @@ -58,7 +58,7 @@ specificExtraNodeopArgs[apiNodeNum] = ( "--transaction-retry-max-storage-size-gb 100 " ) - shipAddr = f"127.0.0.1:{Utils.shardPort(8080)}" + shipAddr = f"127.0.0.1:{Utils.getPort(Utils.PortStateHistory)}" specificExtraNodeopArgs[shipNodeNum] = ( "--plugin sysio::state_history_plugin " f"--state-history-endpoint {shipAddr} " diff --git a/tests/ship_reqs_across_svnn_test.py b/tests/ship_reqs_across_svnn_test.py index 9dfdd8fde6..d28228469b 100755 --- a/tests/ship_reqs_across_svnn_test.py +++ b/tests/ship_reqs_across_svnn_test.py @@ -53,7 +53,7 @@ "--plugin sysio::state_history_plugin " "--trace-history --chain-state-history " "--state-history-stride 200 " - f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} " + f"--state-history-endpoint 127.0.0.1:{Utils.getPort(Utils.PortStateHistory)} " "--plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin --finality-data-history" ) @@ -77,7 +77,7 @@ # Start a SHiP client and request blocks between start_block_num and end_block_num shipClient = "tests/ship_streamer" - shipSocketAddress = f"127.0.0.1:{Utils.shardPort(8080)}" + shipSocketAddress = f"127.0.0.1:{Utils.getPort(Utils.PortStateHistory)}" cmd = ( f"{shipClient} --socket-address {shipSocketAddress} " f"--start-block-num {start_block_num} --end-block-num {end_block_num} " diff --git a/tests/ship_restart_test.py b/tests/ship_restart_test.py index b2c5cd0811..02bd1d29bd 100755 --- a/tests/ship_restart_test.py +++ b/tests/ship_restart_test.py @@ -64,7 +64,7 @@ def corruptedHeaderTest(pos, corruptedValue, shipNode): "--plugin sysio::state_history_plugin " "--trace-history --chain-state-history --finality-data-history " "--state-history-stride 200 " - f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} " + f"--state-history-endpoint 127.0.0.1:{Utils.getPort(Utils.PortStateHistory)} " "--plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin" ) diff --git a/tests/ship_streamer_test.py b/tests/ship_streamer_test.py index 176e24d512..afe411cc01 100755 --- a/tests/ship_streamer_test.py +++ b/tests/ship_streamer_test.py @@ -69,7 +69,7 @@ "--plugin sysio::state_history_plugin " "--trace-history --chain-state-history " "--state-history-stride 200 " - f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} " + f"--state-history-endpoint 127.0.0.1:{Utils.getPort(Utils.PortStateHistory)} " "--plugin sysio::net_api_plugin --plugin sysio::producer_api_plugin " ) if args.finality_data_history: @@ -145,7 +145,7 @@ end_block_num = start_block_num + block_range shipClient = "tests/ship_streamer" - shipSocketAddress = f"127.0.0.1:{Utils.shardPort(8080)}" + shipSocketAddress = f"127.0.0.1:{Utils.getPort(Utils.PortStateHistory)}" def makeShipStreamerCmd(startBlockNum, endBlockNum): """Return a ship_streamer command targeting this test's sharded SHiP endpoint.""" diff --git a/tests/ship_test.py b/tests/ship_test.py index a9549cadf3..e9af2dd4bb 100755 --- a/tests/ship_test.py +++ b/tests/ship_test.py @@ -49,6 +49,12 @@ WalletdName=Utils.SysWalletName shipTempDir=None +shipUnixSocketPath=None +shipUnixSocketPathTemplate="/tmp/sysio-ship-%d.sock" + +def getShipUnixSocketPath(): + """Return a short Unix socket path that stays within sockaddr_un path limits.""" + return shipUnixSocketPathTemplate % (Utils.getPort(Utils.PortStateHistory)) try: TestHelper.printSystemInfo("BEGIN") @@ -61,12 +67,15 @@ specificExtraNodeopArgs[shipNodeNum]=( "--plugin sysio::state_history_plugin " "--sync-fetch-span 200 " - f"--state-history-endpoint 127.0.0.1:{Utils.shardPort(8080)} " + f"--state-history-endpoint 127.0.0.1:{Utils.getPort(Utils.PortStateHistory)} " "--plugin sysio::net_api_plugin " ) if args.unix_socket: - specificExtraNodeopArgs[shipNodeNum] += "--state-history-unix-socket-path ship.sock" + shipUnixSocketPath = getShipUnixSocketPath() + if os.path.exists(shipUnixSocketPath): + os.unlink(shipUnixSocketPath) + specificExtraNodeopArgs[shipNodeNum] += f"--state-history-unix-socket-path {shipUnixSocketPath}" if cluster.launch(pnodes=totalProducerNodes, totalNodes=totalNodes, totalProducers=totalProducers, activateIF=activateIF, @@ -86,9 +95,9 @@ shipClient = "tests/ship_client" cmd = "%s --num-requests %d" % (shipClient, args.num_requests) if args.unix_socket: - cmd += " -a ws+unix:///%s" % (Utils.getNodeDataDir(shipNodeNum, "ship.sock")) + cmd += " -a ws+unix:///%s" % (shipUnixSocketPath) else: - cmd += " -a 127.0.0.1:%d" % (Utils.shardPort(8080)) + cmd += " -a 127.0.0.1:%d" % (Utils.getPort(Utils.PortStateHistory)) if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) clients = [] files = [] @@ -201,6 +210,8 @@ if shipTempDir is not None: if testSuccessful and not args.keep_logs: shutil.rmtree(shipTempDir, ignore_errors=True) + if shipUnixSocketPath is not None and os.path.exists(shipUnixSocketPath): + os.unlink(shipUnixSocketPath) errorCode = 0 if testSuccessful else 1 exit(errorCode) diff --git a/tests/test_port_shard.hpp b/tests/test_port_shard.hpp index d73e291d7c..6025a637d1 100644 --- a/tests/test_port_shard.hpp +++ b/tests/test_port_shard.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -12,20 +11,45 @@ namespace sysio::testing { inline constexpr const char* test_port_offset_env_var = "SYSIO_TEST_PORT_OFFSET"; inline constexpr uint16_t default_state_history_port = 8080; inline constexpr uint32_t compact_shard_anchor_port = 8888; -inline constexpr uint32_t compact_wallet_first_port = 9899; -inline constexpr uint32_t compact_wallet_last_port = 9999; +inline constexpr uint32_t compact_http_first_port = 8888; +inline constexpr uint32_t compact_http_last_port = 8975; inline constexpr uint32_t compact_alternate_first_port = 9776; inline constexpr uint32_t compact_alternate_last_port = 9822; inline constexpr uint32_t compact_p2p_first_port = 9876; inline constexpr uint32_t compact_p2p_last_port = 9898; -inline constexpr uint32_t compact_wallet_slot = 0; -inline constexpr uint32_t compact_bios_http_slot = 100; -inline constexpr uint32_t compact_ship_slot = 150; -inline constexpr uint32_t compact_state_history_slot = 151; -inline constexpr uint32_t compact_alternate_service_slot = 152; -inline constexpr uint32_t compact_alternate_p2p_slot = 153; -inline constexpr uint32_t compact_http_slot = 200; -inline constexpr uint32_t compact_p2p_slot = 225; +inline constexpr uint32_t compact_wallet_first_port = 9900; +inline constexpr uint32_t compact_wallet_last_port = 9903; +inline constexpr uint32_t compact_ipv6_probe_first_port = 9997; +inline constexpr uint32_t compact_ipv6_probe_last_port = 10000; +inline constexpr uint32_t compact_ship_slot = 0; +inline constexpr uint32_t compact_state_history_slot = 1; +inline constexpr uint32_t compact_bios_http_slot = 2; +inline constexpr uint32_t compact_http_slot = 3; +inline constexpr uint32_t compact_alternate_service_slot = 91; +inline constexpr uint32_t compact_plugin_http_peer_slot = 92; +inline constexpr uint32_t compact_plugin_http_local_slot = 93; +inline constexpr uint32_t compact_alternate_p2p_slot = 94; +inline constexpr uint32_t compact_p2p_slot = 141; +inline constexpr uint32_t compact_wallet_base_slot = 164; +inline constexpr uint32_t compact_wallet_slot = 165; +inline constexpr uint32_t compact_ipv6_probe_slot = 171; +inline constexpr uint32_t wallet_port_count = 5; + +/** Logical listener classes inside a test's compact port shard. */ +enum class port_category { + ship, + state_history, + bios_http, + node_http, + alternate_service, + plugin_http_peer, + plugin_http_local, + alternate_p2p, + p2p, + wallet, + transaction_only, + ipv6_probe +}; /** Return the current test's port shard offset from the CTest environment. */ inline uint32_t test_port_offset() { @@ -40,42 +64,77 @@ inline uint32_t test_port_offset() { } } -/** Apply the current test's compact port shard mapping to a base port. */ -inline uint16_t shard_port(uint16_t port) { +/** Return a deterministic port for a listener class and index in this test's shard. */ +inline uint16_t get_port(port_category category, uint32_t index = 0) { const uint32_t offset = test_port_offset(); - if(offset == 0) - return port; - - const uint32_t shard_base = compact_shard_anchor_port + offset; - uint32_t shifted_port = static_cast(port) + offset; + uint32_t slot_start = 0; + uint32_t slot_count = 1; + uint32_t unsharded_base_port = 0; - switch(port) { - case 8788: - shifted_port = shard_base + compact_bios_http_slot; + switch(category) { + case port_category::ship: + slot_start = compact_ship_slot; + unsharded_base_port = 7899; + break; + case port_category::state_history: + slot_start = compact_state_history_slot; + unsharded_base_port = default_state_history_port; + break; + case port_category::bios_http: + slot_start = compact_bios_http_slot; + unsharded_base_port = 8788; + break; + case port_category::node_http: + slot_start = compact_http_slot; + slot_count = compact_http_last_port - compact_http_first_port + 1; + unsharded_base_port = compact_http_first_port; break; - case 7899: - shifted_port = shard_base + compact_ship_slot; + case port_category::alternate_service: + slot_start = compact_alternate_service_slot; + unsharded_base_port = 8976; break; - case default_state_history_port: - shifted_port = shard_base + compact_state_history_slot; + case port_category::plugin_http_peer: + slot_start = compact_plugin_http_peer_slot; + unsharded_base_port = 9009; break; - case 9011: - shifted_port = shard_base + compact_alternate_service_slot; + case port_category::plugin_http_local: + slot_start = compact_plugin_http_local_slot; + unsharded_base_port = 9011; break; - case 8888: - shifted_port = shard_base + compact_http_slot; + case port_category::alternate_p2p: + slot_start = compact_alternate_p2p_slot; + slot_count = compact_alternate_last_port - compact_alternate_first_port + 1; + unsharded_base_port = compact_alternate_first_port; break; - default: - if(compact_alternate_first_port <= port && port <= compact_alternate_last_port) { - shifted_port = shard_base + compact_alternate_p2p_slot + (port - compact_alternate_first_port); - } else if(compact_p2p_first_port <= port && port <= compact_p2p_last_port) { - shifted_port = shard_base + compact_p2p_slot + (port - compact_p2p_first_port); - } else if(compact_wallet_first_port <= port && port <= compact_wallet_last_port) { - shifted_port = shard_base + compact_wallet_slot + std::min(port - compact_wallet_first_port, 99); - } + case port_category::p2p: + slot_start = compact_p2p_slot; + slot_count = compact_p2p_last_port - compact_p2p_first_port + 1; + unsharded_base_port = compact_p2p_first_port; + break; + case port_category::wallet: + slot_start = compact_wallet_base_slot; + slot_count = wallet_port_count; + unsharded_base_port = 9899; + break; + case port_category::transaction_only: + slot_start = compact_wallet_slot + wallet_port_count - 1; + slot_count = 2; + unsharded_base_port = 9902; + break; + case port_category::ipv6_probe: + slot_start = compact_ipv6_probe_slot; + slot_count = compact_ipv6_probe_last_port - compact_ipv6_probe_first_port + 1; + unsharded_base_port = compact_ipv6_probe_first_port; break; } + if(index >= slot_count) + throw std::runtime_error("port category index is outside the compact test shard range"); + + uint32_t shifted_port = unsharded_base_port + index; + if(offset != 0) + shifted_port = compact_shard_anchor_port + offset + slot_start + index; + if(shifted_port > std::numeric_limits::max()) throw std::runtime_error(std::string(test_port_offset_env_var) + " shifts the requested port outside uint16_t"); return static_cast(shifted_port); @@ -83,7 +142,7 @@ inline uint16_t shard_port(uint16_t port) { /** Return the default state-history websocket endpoint for this test shard. */ inline std::string default_state_history_endpoint() { - return std::string("127.0.0.1:") + std::to_string(shard_port(default_state_history_port)); + return std::string("127.0.0.1:") + std::to_string(get_port(port_category::state_history)); } } // namespace sysio::testing diff --git a/tests/trx_generator/CMakeLists.txt b/tests/trx_generator/CMakeLists.txt index 127ec53bc2..ee72c7398f 100644 --- a/tests/trx_generator/CMakeLists.txt +++ b/tests/trx_generator/CMakeLists.txt @@ -8,5 +8,5 @@ target_link_libraries(trx_generator PRIVATE sysio_chain fc Boost::program_option add_executable(trx_generator_tests trx_generator_tests.cpp trx_provider.cpp trx_generator.cpp) target_link_libraries(trx_generator_tests PRIVATE sysio_chain fc Boost::program_options ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS}) target_include_directories(trx_generator_tests PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_SOURCE_DIR}/plugins/net_plugin/include) + ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_SOURCE_DIR}/plugins/net_plugin/include) add_np_test(NAME trx_generator_tests COMMAND trx_generator_tests) diff --git a/tests/trx_generator/trx_generator_tests.cpp b/tests/trx_generator/trx_generator_tests.cpp index 84571a22ad..ca643b5bb8 100644 --- a/tests/trx_generator/trx_generator_tests.cpp +++ b/tests/trx_generator/trx_generator_tests.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #define BOOST_TEST_MODULE trx_generator_tests #include @@ -488,7 +489,7 @@ BOOST_AUTO_TEST_CASE(account_name_generator_tests) BOOST_AUTO_TEST_CASE(simple_http_client_async_test) { const std::string host = "127.0.0.1"s; - constexpr unsigned short port = 8888; + const unsigned short port = get_port(port_category::node_http); // Start Server echo_server_impl server = echo_server_impl(); From 458d893ae3a313d929252b9caa2589ff353047ff Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 5 Jun 2026 21:24:56 +0000 Subject: [PATCH 06/16] Rename port sharding design doc --- docs/{port-sharding-test-plan.md => port-sharding-design.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{port-sharding-test-plan.md => port-sharding-design.md} (100%) diff --git a/docs/port-sharding-test-plan.md b/docs/port-sharding-design.md similarity index 100% rename from docs/port-sharding-test-plan.md rename to docs/port-sharding-design.md From 144299f6b1042d5e17c20856dd5ba6eafb77d3d9 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 5 Jun 2026 21:52:30 +0000 Subject: [PATCH 07/16] Run CI build test package per platform --- .github/workflows/build.yaml | 341 +++++++++++++++-------------------- 1 file changed, 141 insertions(+), 200 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index afb8aa1571..5e0bc0c79d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -48,123 +48,121 @@ jobs: platform-files: | .cicd/platforms - build-base: - name: Run Build Workflow - uses: ./.github/workflows/build_base.yaml + build-test-package: + name: Build, test, and package (${{matrix.platform}}) needs: [platform-cache] - secrets: - GH_TOKEN_DEV: ${{ secrets.GH_TOKEN_DEV }} - with: - platforms: ${{needs.platform-cache.outputs.platforms}} - platform-list: ${{needs.platform-cache.outputs.platform-list}} - - v: - name: Discover Versions - runs-on: ubuntu-latest - outputs: - cdt-target: ${{steps.versions.outputs.cdt-target}} - cdt-prerelease: ${{steps.versions.outputs.cdt-prerelease}} - wire-system-contracts-ref: ${{steps.versions.outputs.wire-system-contracts-ref}} - steps: - - name: Setup wire-cdt and wire-system-contracts versions - id: versions - env: - GH_TOKEN: ${{secrets.GH_TOKEN_DEV}} - run: | - DEFAULTS_JSON=$(curl -sSfL $(gh api https://api.github.com/repos/${{github.repository}}/contents/.cicd/defaults.json?ref=${{github.sha}} --jq .download_url)) - echo cdt-target=$(echo "$DEFAULTS_JSON" | jq -r '.wirecdt.target') >> $GITHUB_OUTPUT - echo cdt-prerelease=$(echo "$DEFAULTS_JSON" | jq -r '.wirecdt.prerelease') >> $GITHUB_OUTPUT - echo wire-system-contracts-ref=$(echo "$DEFAULTS_JSON" | jq -r '.wiresystemcontracts.ref') >> $GITHUB_OUTPUT - - if [[ "${{inputs.override-cdt}}" != "" ]]; then - echo cdt-target=${{inputs.override-cdt}} >> $GITHUB_OUTPUT - fi - if [[ "${{inputs.override-cdt-prerelease}}" == +(true|false) ]]; then - echo cdt-prerelease=${{inputs.override-cdt-prerelease}} >> $GITHUB_OUTPUT - fi - if [[ "${{inputs.override-wire-system-contracts}}" != "" ]]; then - echo wire-system-contracts-ref=${{inputs.override-wire-system-contracts}} >> $GITHUB_OUTPUT - fi - - package: - name: Build deb packages (${{matrix.platform}}) - needs: [platform-cache, build-base] strategy: fail-fast: false matrix: - platform: [ubuntu24, ubsan, asan, asserton, gcc] + include: + - platform: ubuntu24 + build_jobs: 11 + run_sharded_np_lr: true + - platform: ubsan + build_jobs: 11 + run_sharded_np_lr: true + - platform: asan + build_jobs: 11 + run_sharded_np_lr: false + - platform: asserton + build_jobs: 11 + run_sharded_np_lr: true + - platform: gcc + build_jobs: 8 + run_sharded_np_lr: true runs-on: group: core-repo-group - timeout-minutes: 15 container: image: ${{fromJSON(needs.platform-cache.outputs.platforms)[matrix.platform].image}} credentials: username: dev-wire password: ${{ secrets.GH_TOKEN_DEV }} + options: --security-opt seccomp=unconfined --mount type=bind,source=/var/lib/systemd/coredump,target=/cores + volumes: + - ${{ github.workspace }}:/__w/wire-sysio/wire-sysio + env: + CCACHE_MAXSIZE: "10G" + CCACHE_COMPRESS: "true" + CCACHE_COMPRESSLEVEL: "6" steps: + - name: Allow safe directories + run: git config --global --add safe.directory '*' + - uses: actions/checkout@v5 + with: + submodules: recursive - - name: Download builddir - uses: actions/download-artifact@v7 + - name: Restore vcpkg binary cache + uses: actions/cache@v5 with: - name: ${{matrix.platform}}-build + path: ${{ github.workspace }}/vcpkg-binary-cache + key: vcpkg-binaries-${{ matrix.platform }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', '.github/vcpkg-triplets/**') }} + restore-keys: | + vcpkg-binaries-${{ matrix.platform }}- - - name: Build packages - run: | - tar xzf build.tar.gz - cd build - cpack - ../tools/tweak-deb.sh wire-sysio_*.deb + - name: Restore ccache + uses: actions/cache@v5 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-${{ matrix.platform }}-${{ github.sha }} + restore-keys: | + ccache-${{ matrix.platform }}- - - name: Install dev package + - name: Build run: | - apt-get update && apt-get upgrade -y - apt-get install -y ./build/wire-sysio_*.deb + echo "Building for ${{ matrix.platform }}" - - name: Upload dev package - uses: actions/upload-artifact@v6 - with: - name: wire-sysio-dev-${{matrix.platform}}-amd64 - path: build/wire-sysio_*.deb + # Use $GITHUB_WORKSPACE (container path /__w/...) not ${{ github.workspace }} + # (host path /home/runner/work/...) so ccache/vcpkg write to the mounted + # volume and persist for actions/cache to save. + export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" + export VCPKG_BINARY_SOURCES="clear;files,$GITHUB_WORKSPACE/vcpkg-binary-cache,readwrite" + export VCPKG_TARGET_TRIPLET=x64-linux-release + export VCPKG_HOST_TRIPLET=x64-linux-release + export VCPKG_OVERLAY_TRIPLETS="$GITHUB_WORKSPACE/.github/vcpkg-triplets" - tests: - name: Tests (${{matrix.cfg.name}}) - needs: [platform-cache, build-base] - strategy: - fail-fast: false - matrix: - include: - - cfg: {name: 'ubuntu24', base: 'ubuntu24', builddir: 'ubuntu24'} - - cfg: {name: 'ubsan', base: 'ubsan', builddir: 'ubsan'} - - cfg: {name: 'asan', base: 'asan', builddir: 'asan'} - - cfg: {name: 'asserton', base: 'asserton', builddir: 'asserton'} - - cfg: {name: 'gcc', base: 'gcc', builddir: 'gcc'} - runs-on: - group: core-repo-group - container: - image: ${{fromJSON(needs.platform-cache.outputs.platforms)[matrix.cfg.base].image}} - credentials: - username: dev-wire - password: ${{ secrets.GH_TOKEN_DEV }} - options: --security-opt seccomp=unconfined --mount type=bind,source=/var/lib/systemd/coredump,target=/cores - volumes: - - ${{ github.workspace }}:/__w/wire-sysio/wire-sysio + # Clean intermediate vcpkg artifacts but preserve binary cache and downloads + rm -rf vcpkg/buildtrees vcpkg/packages vcpkg/vcpkg_installed \ + build/vcpkg_installed ~/.cache/vcpkg ~/.vcpkg + mkdir -p "$GITHUB_WORKSPACE/vcpkg-binary-cache" - steps: - - name: Ensure writable workspace - run: | - echo "User is $USER" - echo "Fixing permissions on workspace for user $(whoami)" - sudo chown -R $USER:$USER "$GITHUB_WORKSPACE" - sudo chmod -R u+rwX "$GITHUB_WORKSPACE" + ./vcpkg/bootstrap-vcpkg.sh - - uses: actions/checkout@v5 + chown -R $(id -u):$(id -g) $PWD - - name: Download builddir - uses: actions/download-artifact@v7 - with: - name: ${{matrix.cfg.builddir}}-build + # Reset ccache stats for this build + ccache -z || true + + cmake -B build -S . -G Ninja ${SYSIO_PLATFORM_HAS_EXTRAS_CMAKE:+-C /extras.cmake} \ + -DCMAKE_C_COMPILER=$CC \ + -DCMAKE_CXX_COMPILER=$CXX \ + -DCMAKE_MAKE_PROGRAM=$CMAKE_MAKE_PROGRAM \ + -DCMAKE_TOOLCHAIN_FILE=$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DVCPKG_TARGET_TRIPLET=$VCPKG_TARGET_TRIPLET \ + -DVCPKG_HOST_TRIPLET=$VCPKG_HOST_TRIPLET \ + -DVCPKG_OVERLAY_TRIPLETS=$VCPKG_OVERLAY_TRIPLETS \ + -DENABLE_CCACHE=ON \ + -DENABLE_TESTS=ON \ + ${CMAKE_PREFIX_PATH:+-DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH} + + cmake --build build -- -j "${{ matrix.build_jobs }}" + + echo "=== ccache statistics ===" + ccache -s || true + + - name: Show vcpkg logs if failure + if: failure() + run: | + echo "$PWD" + echo "=== vcpkg-manifest-install.log ===" + cat build/vcpkg-manifest-install.log || true + echo "=== Other vcpkg logs ===" + find build -type f -name '*.log' | while read f; do + echo "----- $f -----" + tail -n 40 "$f" + done - name: Debug environment and workspace run: | @@ -220,7 +218,7 @@ jobs: echo echo "=== CHECK WRITABILITY OF BUILD DIR ===" - touch build/debug_write_test 2>&1 || echo "❌ Cannot write to build/" + touch build/debug_write_test 2>&1 || echo "Cannot write to build/" rm -f build/debug_write_test || true - name: Run Parallel Tests @@ -228,14 +226,11 @@ jobs: DOCKER_HOST: ${{ env.DOCKER_HOST }} DOCKER_CONTEXT: ${{ env.DOCKER_CONTEXT }} run: | - # https://github.com/actions/runner/issues/2033 - tar xzf build.tar.gz - sudo chown -R $(id -u):$(id -g) $PWD cd build ctest --output-on-failure -j $(nproc) -LE "(nonparallelizable_tests|long_running_tests)" --timeout 1000 - name: Run Sharded NP/LR Tests - if: matrix.cfg.name != 'asan' + if: ${{ matrix.run_sharded_np_lr }} run: | cd build test_jobs="$(nproc)" @@ -246,7 +241,7 @@ jobs: uses: actions/upload-artifact@v6 if: failure() with: - name: ${{matrix.cfg.name}}-tests-logs + name: ${{matrix.platform}}-tests-logs if-no-files-found: warn path: | /cores @@ -259,108 +254,54 @@ jobs: awk 'BEGIN {err = 1} /bmi2/ && /adx/ {err = 0} END {exit err}' /proc/cpuinfo build/tools/fsgsbase-enabled - # libtester-tests: - # name: libtester tests - # needs: [platform-cache, build-base, v, package] - # strategy: - # fail-fast: false - # matrix: - # platform: [ubuntu20, ubuntu22, ubuntu24] - # test: [build-tree, make-dev-install, deb-install] - # runs-on: ["self-hosted", "enf-x86-midtier"] - # container: ${{ matrix.test != 'deb-install' && fromJSON(needs.platform-cache.outputs.platforms)[matrix.platform].image || matrix.platform == 'ubuntu20' && 'ubuntu:focal' || 'ubuntu:jammy' }} - # env: - # DEBIAN_FRONTEND: noninteractive - # TZ: Etc/UTC - # steps: - # - name: Update Package Index & Upgrade Packages - # run: | - # apt-get update - # apt-get upgrade -y - - # # wire-sysio - # - if: ${{ matrix.test != 'deb-install' }} - # name: Clone sysio - # uses: actions/checkout@v5 - # with: - # submodules: recursive - # - if: ${{ matrix.test != 'deb-install' }} - # name: Download sysio builddir - # uses: actions/download-artifact@v6 - # with: - # name: ${{matrix.platform}}-build - # - if: ${{ matrix.test != 'deb-install' }} - # name: Extract sysio build - # run: | - # zstdcat build.tar.zst | tar x - # - if: ${{ matrix.test == 'build-tree' }} - # name: Set sysio_DIR env var - # run: | - # echo "sysio_DIR=$PWD/build/lib/cmake/sysio" >> "$GITHUB_ENV" - # - if: ${{ matrix.test == 'make-dev-install' }} - # name: sysio dev-install - # run: | - # cmake --install build - # cmake --install build --component dev - # - if: ${{ matrix.test == 'make-dev-install' }} - # name: Delete sysio artifacts - # run: | - # rm -r * - # # - if: ${{ matrix.test == 'deb-install' }} - # # name: Download sysio-dev - # # uses: actions/download-artifact@v6 - # # with: - # # name: sysio-dev-${{matrix.platform}}-amd64 - # # - if: ${{ matrix.test == 'deb-install' }} - # # name: Install sysio-dev Package - # # run: | - # # apt-get install -y ./*.deb - # # rm ./*.deb - - # # CDT - # # - name: Download cdt - # # uses: AntelopeIO/asset-artifact-download-action@v3 - # # with: - # # owner: Wire-Network - # # repo: wire-cdt - # # file: 'cdt_.*amd64.deb' - # # target: '${{needs.v.outputs.cdt-target}}' - # # prereleases: ${{fromJSON(needs.v.outputs.cdt-prerelease)}} - # # artifact-name: cdt_ubuntu_package_amd64 - # # - name: Install cdt Packages - # # run: | - # # apt-get install -y ./*.deb - # # rm ./*.deb - # # CDT - # - name: Download and Install wire-cdt - # run: | - # CDT_VERSION="${{needs.v.outputs.cdt-target}}" - # CDT_URL="https://github.com/Wire-Network/wire-cdt/releases/download/v${CDT_VERSION}/wire-cdt_${CDT_VERSION}_amd64.deb" - # wget -O wire-cdt.deb "$CDT_URL" - # apt-get install -y ./wire-cdt.deb - # rm ./wire-cdt.deb - - # # Reference Contracts - # - name: checkout wire-system-contracts - # uses: actions/checkout@v5 - # with: - # repository: Wire-Network/wire-system-contracts - # path: wire-system-contracts - # ref: '${{needs.v.outputs.wire-system-contracts-ref}}' - # - if: ${{ matrix.test == 'deb-install' }} - # name: Install wire-system-contracts deps - # run: | - # apt-get -y install cmake build-essential - # - name: Build & Test wire-system-contracts - # run: | - # cmake -S wire-system-contracts -B wire-system-contracts/build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=On -DSYSTEM_ENABLE_SYSIO_VERSION_CHECK=Off -DSYSTEM_ENABLE_CDT_VERSION_CHECK=Off - # cmake --build wire-system-contracts/build -- -j $(nproc) - # cd wire-system-contracts/build/tests - # ctest --output-on-failure -j $(nproc) + - name: Build packages + run: | + cd build + cpack + ../tools/tweak-deb.sh wire-sysio_*.deb + + - name: Install dev package + run: | + apt-get update && apt-get upgrade -y + apt-get install -y ./build/wire-sysio_*.deb + + - name: Upload dev package + uses: actions/upload-artifact@v6 + with: + name: wire-sysio-dev-${{matrix.platform}}-amd64 + path: build/wire-sysio_*.deb + + v: + name: Discover Versions + runs-on: ubuntu-latest + outputs: + cdt-target: ${{steps.versions.outputs.cdt-target}} + cdt-prerelease: ${{steps.versions.outputs.cdt-prerelease}} + wire-system-contracts-ref: ${{steps.versions.outputs.wire-system-contracts-ref}} + steps: + - name: Setup wire-cdt and wire-system-contracts versions + id: versions + env: + GH_TOKEN: ${{secrets.GH_TOKEN_DEV}} + run: | + DEFAULTS_JSON=$(curl -sSfL $(gh api https://api.github.com/repos/${{github.repository}}/contents/.cicd/defaults.json?ref=${{github.sha}} --jq .download_url)) + echo cdt-target=$(echo "$DEFAULTS_JSON" | jq -r '.wirecdt.target') >> $GITHUB_OUTPUT + echo cdt-prerelease=$(echo "$DEFAULTS_JSON" | jq -r '.wirecdt.prerelease') >> $GITHUB_OUTPUT + echo wire-system-contracts-ref=$(echo "$DEFAULTS_JSON" | jq -r '.wiresystemcontracts.ref') >> $GITHUB_OUTPUT + + if [[ "${{inputs.override-cdt}}" != "" ]]; then + echo cdt-target=${{inputs.override-cdt}} >> $GITHUB_OUTPUT + fi + if [[ "${{inputs.override-cdt-prerelease}}" == +(true|false) ]]; then + echo cdt-prerelease=${{inputs.override-cdt-prerelease}} >> $GITHUB_OUTPUT + fi + if [[ "${{inputs.override-sys-system-contracts}}" != "" ]]; then + echo wire-system-contracts-ref=${{inputs.override-sys-system-contracts}} >> $GITHUB_OUTPUT + fi all-passing: name: All Required Tests Passed - needs: [ tests ] + needs: [ build-test-package ] if: always() runs-on: ubuntu-latest steps: @@ -371,8 +312,8 @@ jobs: webhook-url: ${{ secrets.WEBHOOK_URL }} notification-type: 1 workflow-name: "Build & Test Workflow" - job-results: "tests:${{ needs.tests.result }}" + job-results: "build-test-package:${{ needs.build-test-package.result }}" github-context: ${{ toJSON(github) }} - - if: needs.tests.result != 'success' + - if: needs.build-test-package.result != 'success' run: false From 9c55111f40f200603c9800f07be3b96d0b4b137c Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Sat, 6 Jun 2026 14:39:48 -0500 Subject: [PATCH 08/16] Document opt-in test port avoidance --- cmake/test-helpers.cmake | 62 ++++++++++++++++++++++++++++++++++++ docs/port-sharding-design.md | 30 +++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/cmake/test-helpers.cmake b/cmake/test-helpers.cmake index c1602709c6..f52ebca552 100644 --- a/cmake/test-helpers.cmake +++ b/cmake/test-helpers.cmake @@ -1,5 +1,61 @@ set(SYSIO_TEST_PORT_OFFSET_START 100) set(SYSIO_TEST_PORT_OFFSET_STRIDE 192) +set(SYSIO_TEST_PORT_SHARD_BASE 8888) + +# Host-specific reservations are opt-in so CI and fresh build directories keep deterministic compact shard allocation. +set(SYSIO_TEST_FORBIDDEN_PORTS "" CACHE STRING "Semicolon-separated actual TCP ports that test port shards must not overlap") +option(SYSIO_DETECT_LISTENING_TEST_PORTS "Avoid test port shards that overlap TCP ports listening during CMake configure" OFF) + +# Append TCP listener ports that are present during configure; this is a local developer convenience, not a CI contract. +function(append_listening_test_ports out_var) + set(listening_ports) + + if(APPLE) + execute_process( + COMMAND lsof -nP -iTCP -sTCP:LISTEN + OUTPUT_VARIABLE listening_ports_output + ERROR_QUIET + ) + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + execute_process( + COMMAND ss -H -ltn + OUTPUT_VARIABLE listening_ports_output + ERROR_QUIET + ) + endif() + + if(listening_ports_output) + string(REGEX MATCHALL "[:.]([0-9]+)([ \t\r\n]|$)" listening_port_matches "${listening_ports_output}") + foreach(listening_port_match IN LISTS listening_port_matches) + string(REGEX REPLACE "^[:.]([0-9]+).*" "\\1" listening_port "${listening_port_match}") + if(listening_port MATCHES "^[0-9]+$") + list(APPEND listening_ports "${listening_port}") + endif() + endforeach() + list(REMOVE_DUPLICATES listening_ports) + endif() + + set(${out_var} ${${out_var}} ${listening_ports} PARENT_SCOPE) +endfunction() + +if(SYSIO_DETECT_LISTENING_TEST_PORTS) + append_listening_test_ports(SYSIO_TEST_FORBIDDEN_PORTS) +endif() + +function(test_port_offset_overlaps_forbidden_port out_var port_offset) + math(EXPR shard_first_port "${SYSIO_TEST_PORT_SHARD_BASE} + ${port_offset}") + math(EXPR shard_last_port "${shard_first_port} + ${SYSIO_TEST_PORT_OFFSET_STRIDE} - 1") + + set(overlaps_forbidden_port FALSE) + foreach(forbidden_port IN LISTS SYSIO_TEST_FORBIDDEN_PORTS) + if(forbidden_port GREATER_EQUAL shard_first_port AND forbidden_port LESS_EQUAL shard_last_port) + set(overlaps_forbidden_port TRUE) + break() + endif() + endforeach() + + set(${out_var} ${overlaps_forbidden_port} PARENT_SCOPE) +endfunction() function(next_test_port_offset out_var) get_property(next_offset GLOBAL PROPERTY SYSIO_NEXT_TEST_PORT_OFFSET) @@ -7,6 +63,12 @@ function(next_test_port_offset out_var) set(next_offset "${SYSIO_TEST_PORT_OFFSET_START}") endif() + test_port_offset_overlaps_forbidden_port(overlaps_forbidden_port "${next_offset}") + while(overlaps_forbidden_port) + math(EXPR next_offset "${next_offset} + ${SYSIO_TEST_PORT_OFFSET_STRIDE}") + test_port_offset_overlaps_forbidden_port(overlaps_forbidden_port "${next_offset}") + endwhile() + math(EXPR next_next_offset "${next_offset} + ${SYSIO_TEST_PORT_OFFSET_STRIDE}") set_property(GLOBAL PROPERTY SYSIO_NEXT_TEST_PORT_OFFSET ${next_next_offset}) set(${out_var} ${next_offset} PARENT_SCOPE) diff --git a/docs/port-sharding-design.md b/docs/port-sharding-design.md index 2ad5b8fda2..338d88612d 100644 --- a/docs/port-sharding-design.md +++ b/docs/port-sharding-design.md @@ -35,6 +35,36 @@ Each offset reserves one 192-port shard. For a nonzero offset, category slots ar shard_base = 8888 + SYSIO_TEST_PORT_OFFSET ``` +## Avoiding Local Listener Ports + +By default, CMake assigns compact shards without reserving machine-specific ports. This keeps the allocation +deterministic across developer machines and CI: + +```cmake +SYSIO_TEST_FORBIDDEN_PORTS="" +SYSIO_DETECT_LISTENING_TEST_PORTS=OFF +``` + +When a developer machine has a long-lived local service inside the generated shard range, pass a semicolon-separated +list of actual TCP ports that tests must not overlap: + +```bash +cmake -S . -B build/codex-system-contracts -DSYSIO_TEST_FORBIDDEN_PORTS='11434;19981' +``` + +The allocator skips any shard whose actual port range contains one of those ports. For example, with +`SYSIO_TEST_FORBIDDEN_PORTS=11434`, the shard `11292..11483` is skipped because it contains `11434`. + +For local-only convenience, CMake can also snapshot currently listening TCP ports during configure and append them to +the forbidden list: + +```bash +cmake -S . -B build/codex-system-contracts -DSYSIO_DETECT_LISTENING_TEST_PORTS=ON +``` + +Detection is intentionally opt-in. It depends on the host state at configure time, so it is useful for local developer +machines but should not be required for reproducible CI allocation. + ## Category Mapping New test listener ports must use the logical category API: From a0ace8da27f123979801d1e73249c7d0345b478a Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Mon, 8 Jun 2026 15:23:50 +0000 Subject: [PATCH 09/16] Address port-sharded CI review --- .github/scripts/build-sysio.sh | 45 ++++++++++++++++++++++++++ .github/workflows/build.yaml | 53 +++++++------------------------ .github/workflows/build_base.yaml | 47 +++------------------------ cmake/test-helpers.cmake | 15 ++++++--- docs/port-sharding-design.md | 23 +++++++++++--- tests/TestHarness/Cluster.py | 6 ++-- tests/TestHarness/launcher.py | 8 +++-- tests/TestHarness/testUtils.py | 7 ++-- tests/p2p_multiple_listen_test.py | 5 +-- tests/test_port_shard.hpp | 34 ++++++++++++++------ 10 files changed, 132 insertions(+), 111 deletions(-) create mode 100644 .github/scripts/build-sysio.sh diff --git a/.github/scripts/build-sysio.sh b/.github/scripts/build-sysio.sh new file mode 100644 index 0000000000..29be140d4b --- /dev/null +++ b/.github/scripts/build-sysio.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +BUILD_JOBS="${1:?usage: build-sysio.sh }" + +echo "Building for ${SYSIO_PLATFORM_NAME:-unknown platform}" + +# Use $GITHUB_WORKSPACE (container path /__w/...) so ccache/vcpkg write to +# the mounted volume and persist for actions/cache to save. +export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" +export VCPKG_BINARY_SOURCES="clear;files,$GITHUB_WORKSPACE/vcpkg-binary-cache,readwrite" +export VCPKG_TARGET_TRIPLET=x64-linux-release +export VCPKG_HOST_TRIPLET=x64-linux-release +export VCPKG_OVERLAY_TRIPLETS="$GITHUB_WORKSPACE/.github/vcpkg-triplets" + +# Clean intermediate vcpkg artifacts but preserve binary cache and downloads. +rm -rf vcpkg/buildtrees vcpkg/packages vcpkg/vcpkg_installed \ + build/vcpkg_installed ~/.cache/vcpkg ~/.vcpkg + +mkdir -p "$GITHUB_WORKSPACE/vcpkg-binary-cache" + +./vcpkg/bootstrap-vcpkg.sh + +chown -R "$(id -u):$(id -g)" "$PWD" + +# Reset ccache stats for this build. +ccache -z || true + +cmake -B build -S . -G Ninja ${SYSIO_PLATFORM_HAS_EXTRAS_CMAKE:+-C /extras.cmake} \ +-DCMAKE_C_COMPILER="$CC" \ +-DCMAKE_CXX_COMPILER="$CXX" \ +-DCMAKE_MAKE_PROGRAM="$CMAKE_MAKE_PROGRAM" \ +-DCMAKE_TOOLCHAIN_FILE="$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake" \ +-DCMAKE_BUILD_TYPE=Release \ +-DVCPKG_TARGET_TRIPLET="$VCPKG_TARGET_TRIPLET" \ +-DVCPKG_HOST_TRIPLET="$VCPKG_HOST_TRIPLET" \ +-DVCPKG_OVERLAY_TRIPLETS="$VCPKG_OVERLAY_TRIPLETS" \ +-DENABLE_CCACHE=ON \ +-DENABLE_TESTS=ON \ +${CMAKE_PREFIX_PATH:+-DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH} + +cmake --build build -- -j "$BUILD_JOBS" + +echo "=== ccache statistics ===" +ccache -s || true diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5e0bc0c79d..bfdc8c41a1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -109,48 +109,11 @@ jobs: ccache-${{ matrix.platform }}- - name: Build + id: build + env: + SYSIO_PLATFORM_NAME: ${{ matrix.platform }} run: | - echo "Building for ${{ matrix.platform }}" - - # Use $GITHUB_WORKSPACE (container path /__w/...) not ${{ github.workspace }} - # (host path /home/runner/work/...) so ccache/vcpkg write to the mounted - # volume and persist for actions/cache to save. - export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" - export VCPKG_BINARY_SOURCES="clear;files,$GITHUB_WORKSPACE/vcpkg-binary-cache,readwrite" - export VCPKG_TARGET_TRIPLET=x64-linux-release - export VCPKG_HOST_TRIPLET=x64-linux-release - export VCPKG_OVERLAY_TRIPLETS="$GITHUB_WORKSPACE/.github/vcpkg-triplets" - - # Clean intermediate vcpkg artifacts but preserve binary cache and downloads - rm -rf vcpkg/buildtrees vcpkg/packages vcpkg/vcpkg_installed \ - build/vcpkg_installed ~/.cache/vcpkg ~/.vcpkg - - mkdir -p "$GITHUB_WORKSPACE/vcpkg-binary-cache" - - ./vcpkg/bootstrap-vcpkg.sh - - chown -R $(id -u):$(id -g) $PWD - - # Reset ccache stats for this build - ccache -z || true - - cmake -B build -S . -G Ninja ${SYSIO_PLATFORM_HAS_EXTRAS_CMAKE:+-C /extras.cmake} \ - -DCMAKE_C_COMPILER=$CC \ - -DCMAKE_CXX_COMPILER=$CXX \ - -DCMAKE_MAKE_PROGRAM=$CMAKE_MAKE_PROGRAM \ - -DCMAKE_TOOLCHAIN_FILE=$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DVCPKG_TARGET_TRIPLET=$VCPKG_TARGET_TRIPLET \ - -DVCPKG_HOST_TRIPLET=$VCPKG_HOST_TRIPLET \ - -DVCPKG_OVERLAY_TRIPLETS=$VCPKG_OVERLAY_TRIPLETS \ - -DENABLE_CCACHE=ON \ - -DENABLE_TESTS=ON \ - ${CMAKE_PREFIX_PATH:+-DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH} - - cmake --build build -- -j "${{ matrix.build_jobs }}" - - echo "=== ccache statistics ===" - ccache -s || true + bash .github/scripts/build-sysio.sh "${{ matrix.build_jobs }}" - name: Show vcpkg logs if failure if: failure() @@ -230,9 +193,13 @@ jobs: ctest --output-on-failure -j $(nproc) -LE "(nonparallelizable_tests|long_running_tests)" --timeout 1000 - name: Run Sharded NP/LR Tests - if: ${{ matrix.run_sharded_np_lr }} + if: ${{ steps.build.outcome == 'success' && matrix.run_sharded_np_lr && !cancelled() }} run: | cd build + # Run against all CPUs available to the CI container. These NP/LR tests are + # expected to be deterministic under CI scheduling pressure; flakes caused + # by host load, sanitizer overhead, or process interleaving are test bugs + # that should remain visible instead of being hidden by an artificial cap. test_jobs="$(nproc)" echo "Running sharded NP/LR tests with ${test_jobs} jobs" ctest --output-on-failure -j "${test_jobs}" -L "(nonparallelizable_tests|long_running_tests)" --timeout 2700 @@ -250,11 +217,13 @@ jobs: build/PerformanceHarnessScenarioRunnerLogs/ - name: Check CPU Features + if: ${{ steps.build.outcome == 'success' && !cancelled() }} run: | awk 'BEGIN {err = 1} /bmi2/ && /adx/ {err = 0} END {exit err}' /proc/cpuinfo build/tools/fsgsbase-enabled - name: Build packages + if: ${{ steps.build.outcome == 'success' && !cancelled() }} run: | cd build cpack diff --git a/.github/workflows/build_base.yaml b/.github/workflows/build_base.yaml index 9f618b2b8e..43c3ffcc23 100644 --- a/.github/workflows/build_base.yaml +++ b/.github/workflows/build_base.yaml @@ -67,53 +67,16 @@ jobs: - name: Build id: build + env: + SYSIO_PLATFORM_NAME: ${{ matrix.platform }} run: | - echo "Building for ${{ matrix.platform }}" - - # Use $GITHUB_WORKSPACE (container path /__w/...) not ${{ github.workspace }} - # (host path /home/runner/work/...) so ccache/vcpkg write to the mounted - # volume and persist for actions/cache to save. - export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache" - export VCPKG_BINARY_SOURCES="clear;files,$GITHUB_WORKSPACE/vcpkg-binary-cache,readwrite" - export VCPKG_TARGET_TRIPLET=x64-linux-release - export VCPKG_HOST_TRIPLET=x64-linux-release - export VCPKG_OVERLAY_TRIPLETS="$GITHUB_WORKSPACE/.github/vcpkg-triplets" - - # Clean intermediate vcpkg artifacts but preserve binary cache and downloads - rm -rf vcpkg/buildtrees vcpkg/packages vcpkg/vcpkg_installed \ - build/vcpkg_installed ~/.cache/vcpkg ~/.vcpkg - - mkdir -p "$GITHUB_WORKSPACE/vcpkg-binary-cache" - - ./vcpkg/bootstrap-vcpkg.sh - - chown -R $(id -u):$(id -g) $PWD - - # Reset ccache stats for this build - ccache -z || true - - cmake -B build -S . -G Ninja ${SYSIO_PLATFORM_HAS_EXTRAS_CMAKE:+-C /extras.cmake} \ - -DCMAKE_C_COMPILER=$CC \ - -DCMAKE_CXX_COMPILER=$CXX \ - -DCMAKE_MAKE_PROGRAM=$CMAKE_MAKE_PROGRAM \ - -DCMAKE_TOOLCHAIN_FILE=$PWD/vcpkg/scripts/buildsystems/vcpkg.cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DVCPKG_TARGET_TRIPLET=$VCPKG_TARGET_TRIPLET \ - -DVCPKG_HOST_TRIPLET=$VCPKG_HOST_TRIPLET \ - -DVCPKG_OVERLAY_TRIPLETS=$VCPKG_OVERLAY_TRIPLETS \ - -DENABLE_CCACHE=ON \ - -DENABLE_TESTS=ON \ - ${CMAKE_PREFIX_PATH:+-DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH} - if [[ "${{ matrix.platform }}" == "gcc" ]]; then - echo "Using reduced parallelism for gcc platform" - cmake --build build -- -j 8 + build_jobs=8 else - cmake --build build -- -j 11 + build_jobs=11 fi - echo "=== ccache statistics ===" - ccache -s || true + bash .github/scripts/build-sysio.sh "$build_jobs" tar -pcz --exclude "*.o" build > build.tar.gz diff --git a/cmake/test-helpers.cmake b/cmake/test-helpers.cmake index c1602709c6..8eb3854d67 100644 --- a/cmake/test-helpers.cmake +++ b/cmake/test-helpers.cmake @@ -1,5 +1,7 @@ set(SYSIO_TEST_PORT_OFFSET_START 100) set(SYSIO_TEST_PORT_OFFSET_STRIDE 192) +set(SYSIO_TEST_PORT_ANCHOR 8888) +set(SYSIO_TEST_PORT_EPHEMERAL_FLOOR 32768) function(next_test_port_offset out_var) get_property(next_offset GLOBAL PROPERTY SYSIO_NEXT_TEST_PORT_OFFSET) @@ -7,13 +9,20 @@ function(next_test_port_offset out_var) set(next_offset "${SYSIO_TEST_PORT_OFFSET_START}") endif() + math(EXPR shard_max_port "${SYSIO_TEST_PORT_ANCHOR} + ${next_offset} + ${SYSIO_TEST_PORT_OFFSET_STRIDE} - 1") + if(shard_max_port GREATER_EQUAL ${SYSIO_TEST_PORT_EPHEMERAL_FLOOR}) + message(FATAL_ERROR + "CTest port shard ${next_offset} would reach port ${shard_max_port}, " + "which is at or above the ${SYSIO_TEST_PORT_EPHEMERAL_FLOOR} ephemeral-port floor") + endif() + math(EXPR next_next_offset "${next_offset} + ${SYSIO_TEST_PORT_OFFSET_STRIDE}") set_property(GLOBAL PROPERTY SYSIO_NEXT_TEST_PORT_OFFSET ${next_next_offset}) set(${out_var} ${next_offset} PARENT_SCOPE) endfunction() function(setup_test_common) - cmake_parse_arguments(PARSE_ARGV 0 arg "AUTO_PORT_OFFSET;AUTO_LR_PORT_OFFSET" "NAME;COST;TIMEOUT;PORT_OFFSET" "COMMAND;ENVIRONMENT") + cmake_parse_arguments(PARSE_ARGV 0 arg "AUTO_PORT_OFFSET" "NAME;COST;TIMEOUT;PORT_OFFSET" "COMMAND;ENVIRONMENT") add_test(NAME "${arg_NAME}" COMMAND ${arg_COMMAND} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") @@ -25,8 +34,6 @@ function(setup_test_common) endif() if(arg_PORT_OFFSET) set(test_port_offset ${arg_PORT_OFFSET}) - elseif(arg_AUTO_LR_PORT_OFFSET) - next_test_port_offset(test_port_offset) elseif(arg_AUTO_PORT_OFFSET) next_test_port_offset(test_port_offset) endif() @@ -60,6 +67,6 @@ endfunction() function(add_lr_test) cmake_parse_arguments(PARSE_ARGV 0 arg "" "NAME;COST;TIMEOUT" "COMMAND") - setup_test_common(${ARGV} AUTO_LR_PORT_OFFSET) + setup_test_common(${ARGV} AUTO_PORT_OFFSET) set_property(TEST "${arg_NAME}" PROPERTY LABELS long_running_tests) endfunction() diff --git a/docs/port-sharding-design.md b/docs/port-sharding-design.md index 2ad5b8fda2..fa10008dd5 100644 --- a/docs/port-sharding-design.md +++ b/docs/port-sharding-design.md @@ -18,10 +18,12 @@ CTest offsets are assigned in `cmake/test-helpers.cmake` by one shared allocator ```cmake set(SYSIO_TEST_PORT_OFFSET_START 100) set(SYSIO_TEST_PORT_OFFSET_STRIDE 192) +set(SYSIO_TEST_PORT_EPHEMERAL_FLOOR 32768) ``` Both `add_np_test()` and `add_lr_test()` call this allocator. The allocator is global, not label-specific, so an NP -test and an LR test cannot receive the same offset during one CMake configure. +test and an LR test cannot receive the same offset during one CMake configure. The helper keeps one public +`AUTO_PORT_OFFSET` flag internally; both labels use it because they intentionally share one offset sequence. The generated sequence is compact and deterministic: @@ -35,6 +37,9 @@ Each offset reserves one 192-port shard. For a nonzero offset, category slots ar shard_base = 8888 + SYSIO_TEST_PORT_OFFSET ``` +CMake fails configuration if a new test would push the conservative shard ceiling +`8888 + SYSIO_TEST_PORT_OFFSET + 191` into the default Linux ephemeral-port range. + ## Category Mapping New test listener ports must use the logical category API: @@ -57,7 +62,8 @@ Current category slots are: | `alternate_service` | `8976` | `91` | `1` | explicit alternate service listener | | `plugin_http_peer` | `9009` | `92` | `1` | plugin HTTP peer endpoint | | `plugin_http_local` | `9011` | `93` | `1` | plugin HTTP local endpoint | -| `alternate_p2p` | `9776` | `94..140` | `47` | alternate P2P/listener endpoints | +| `bios_p2p` | `9776` | `94` | `1` | BIOS P2P endpoint | +| `alternate_p2p` | `9777` | `95..140` | `46` | alternate P2P/listener endpoints | | `p2p` | `9876` | `141..163` | `23` | normal node P2P endpoints | | `wallet` | `9899` | `164..168` | `5` | wallet/kiod endpoints | | `transaction_only` | `9902` | `169..170` | `2` | transaction-only P2P endpoints | @@ -78,7 +84,8 @@ For example, with offsets `100` and `292`: | `state_history` | `8989` | `9181` | | `bios_http` | `8990` | `9182` | | `node_http[0]` | `8991` | `9183` | -| `alternate_p2p[0]` | `9082` | `9274` | +| `bios_p2p` | `9082` | `9274` | +| `alternate_p2p[0]` | `9083` | `9275` | | `p2p[0]` | `9129` | `9321` | | `wallet[0]` | `9152` | `9344` | | `transaction_only[0]` | `9157` | `9349` | @@ -127,6 +134,7 @@ New test endpoints should use one of these patterns: - `Cluster.getHttpEndpoint(node_id)` for node HTTP endpoints. - `Cluster.getNodeP2pEndpoint(node_id)` for node P2P endpoints. - `Cluster.getBiosP2pEndpoint()` for the BIOS P2P endpoint. +- `Utils.getPort(Utils.PortBiosHttp)` for the BIOS HTTP endpoint. - `sysio::testing::get_port(port_category::state_history)` in C++ test helpers or test binaries. Do not hardcode shifted port numbers in tests. Keep the unsharded category default local and let the harness assign @@ -144,7 +152,7 @@ ctest --test-dir build/codex-system-contracts -N -V -L 'nonparallelizable_tests| | uniq -d ``` -Summarize the current offset range and highest compact hot listener port: +Summarize the current offset range and conservative compact shard ceiling: ```bash python3 - <<'PY' @@ -170,6 +178,9 @@ print("max_hot_port", 8888 + max(offsets) + 191) PY ``` +The `+191` value is the full reserved shard width, not the highest listener slot currently assigned. The current +highest assigned slot is `174`, but the wider bound keeps the ephemeral-range check stable as new categories are added. + Run the full NP/LR set with high local concurrency: ```bash @@ -218,4 +229,6 @@ That was fixed by moving the Unix socket to the short `/tmp/sysio-ship-.so Port sharding removes listener collisions; it does not make every NP/LR test safe under unbounded machine load. At very high parallelism, many tests bootstrap chains, publish large contracts, run transaction generators, and start -many `nodeop` processes at once. Treat `-j90` as a collision stress probe, not as the expected CI concurrency level. +many `nodeop` processes at once. Treat `-j90` as a collision stress probe above the expected CI concurrency level. +CI intentionally runs the sharded NP/LR set with `-j $(nproc)` so scheduler, sanitizer, and process-interleaving +flakes are surfaced as failures. These tests are expected to be deterministic under normal CI CPU pressure. diff --git a/tests/TestHarness/Cluster.py b/tests/TestHarness/Cluster.py index 574dce72fa..d6de9c39b5 100644 --- a/tests/TestHarness/Cluster.py +++ b/tests/TestHarness/Cluster.py @@ -193,9 +193,9 @@ def _shutdownLaunchFailure(self): def _portsForLocalLaunch(self, totalNodes): """Return the local HTTP and P2P ports reserved by a cluster launch.""" ports = set(range(self.port, self.port + totalNodes)) - ports.add(self.port - 100) + ports.add(Utils.getPort(Utils.PortBiosHttp)) ports.update(range(self.p2pBasePort, self.p2pBasePort + totalNodes)) - ports.add(self.p2pBasePort - 100) + ports.add(Utils.getPort(Utils.PortBiosP2P)) return ports def launch(self, pnodes=1, unstartedNodes=0, totalNodes=1, prodCount=21, topo="mesh", delay=2, onlyBios=False, dontBootstrap=False, @@ -740,7 +740,7 @@ def getNodeP2pEndpoint(self, nodeId: int): def getBiosP2pEndpoint(self): """Return the sharded P2P endpoint for the BIOS node.""" - return f"{self.host}:{self.p2pBasePort - 100}" + return f"{self.host}:{Utils.getPort(Utils.PortBiosP2P)}" def getHttpEndpoint(self, nodeId: int): """Return the sharded HTTP endpoint for a non-BIOS node.""" diff --git a/tests/TestHarness/launcher.py b/tests/TestHarness/launcher.py index 7f44df2917..e66f0ea64a 100644 --- a/tests/TestHarness/launcher.py +++ b/tests/TestHarness/launcher.py @@ -52,6 +52,8 @@ class nodeDefinition: http_port: int = 0 base_p2p_port: ClassVar[int] = Utils.getPort(Utils.PortP2P) base_http_port: ClassVar[int] = Utils.getPort(Utils.PortNodeHttp) + bios_p2p_port: ClassVar[int] = Utils.getPort(Utils.PortBiosP2P) + bios_http_port: ClassVar[int] = Utils.getPort(Utils.PortBiosHttp) host_name: str = 'localhost' public_name: str = 'localhost' listen_addr: str = '0.0.0.0' @@ -75,11 +77,13 @@ def set_host(self, is_bios=False): @classmethod def p2p_bios_port(cls): - return cls.base_p2p_port - 100 + """Return the BIOS P2P listener port assigned to this test shard.""" + return cls.bios_p2p_port @classmethod def http_bios_port(cls): - return cls.base_http_port - 100 + """Return the BIOS HTTP listener port assigned to this test shard.""" + return cls.bios_http_port @classmethod def create_p2p_port_generator(cls): diff --git a/tests/TestHarness/testUtils.py b/tests/TestHarness/testUtils.py index 918c359d4d..e5ce22419a 100644 --- a/tests/TestHarness/testUtils.py +++ b/tests/TestHarness/testUtils.py @@ -94,6 +94,7 @@ class Utils: PortShip="ship" PortStateHistory="state_history" PortBiosHttp="bios_http" + PortBiosP2P="bios_p2p" PortNodeHttp="node_http" PortAlternateService="alternate_service" PortPluginHttpPeer="plugin_http_peer" @@ -148,7 +149,8 @@ def getPort(port_category, index=0): Utils.PortAlternateService: (91, 1), Utils.PortPluginHttpPeer: (92, 1), Utils.PortPluginHttpLocal: (93, 1), - Utils.PortAlternateP2P: (94, 47), + Utils.PortBiosP2P: (94, 1), + Utils.PortAlternateP2P: (95, 46), Utils.PortP2P: (141, 23), Utils.PortWallet: (164, Utils.WalletPortCount), Utils.PortTransactionOnly: (169, 2), @@ -169,11 +171,12 @@ def getPort(port_category, index=0): Utils.PortShip: 7899, Utils.PortStateHistory: 8080, Utils.PortBiosHttp: 8788, + Utils.PortBiosP2P: 9776, Utils.PortNodeHttp: 8888, Utils.PortAlternateService: 8976, Utils.PortPluginHttpPeer: 9009, Utils.PortPluginHttpLocal: 9011, - Utils.PortAlternateP2P: 9776, + Utils.PortAlternateP2P: 9777, Utils.PortP2P: 9876, Utils.PortWallet: 9899, Utils.PortTransactionOnly: 9902, diff --git a/tests/p2p_multiple_listen_test.py b/tests/p2p_multiple_listen_test.py index 803e5e2804..e13871ef91 100755 --- a/tests/p2p_multiple_listen_test.py +++ b/tests/p2p_multiple_listen_test.py @@ -13,6 +13,7 @@ Print=Utils.Print errorExit=Utils.errorExit +# These endpoints are advertised in handshakes only; the test never binds them. advertisedP2pEndpoint0 = 'ext-ip0:20000' advertisedP2pEndpoint1 = 'ext-ip1:20001' @@ -53,8 +54,8 @@ def getOpenPeerAddress(node): Print(f'producing nodes: {pnodes}, delay between nodes launch: {delay} second{"s" if delay != 1 else ""}') Print("Stand up cluster") - alternateListenEndpoint = f"0.0.0.0:{Utils.getPort(Utils.PortAlternateP2P, 3)}" - alternatePeerEndpoint = f"localhost:{Utils.getPort(Utils.PortAlternateP2P, 3)}" + alternateListenEndpoint = f"0.0.0.0:{Utils.getPort(Utils.PortAlternateP2P, 2)}" + alternatePeerEndpoint = f"localhost:{Utils.getPort(Utils.PortAlternateP2P, 2)}" specificArgs = { '0': f'--agent-name node-00 --p2p-listen-endpoint 0.0.0.0:{cluster.getNodeP2pPort(0)} ' f'--p2p-listen-endpoint {alternateListenEndpoint} --p2p-server-address {advertisedP2pEndpoint0} ' diff --git a/tests/test_port_shard.hpp b/tests/test_port_shard.hpp index 6025a637d1..df712219ef 100644 --- a/tests/test_port_shard.hpp +++ b/tests/test_port_shard.hpp @@ -1,10 +1,13 @@ #pragma once +#include #include #include #include #include #include +#include +#include namespace sysio::testing { @@ -13,7 +16,8 @@ inline constexpr uint16_t default_state_history_port = 8080; inline constexpr uint32_t compact_shard_anchor_port = 8888; inline constexpr uint32_t compact_http_first_port = 8888; inline constexpr uint32_t compact_http_last_port = 8975; -inline constexpr uint32_t compact_alternate_first_port = 9776; +inline constexpr uint32_t compact_bios_p2p_port = 9776; +inline constexpr uint32_t compact_alternate_first_port = 9777; inline constexpr uint32_t compact_alternate_last_port = 9822; inline constexpr uint32_t compact_p2p_first_port = 9876; inline constexpr uint32_t compact_p2p_last_port = 9898; @@ -28,10 +32,10 @@ inline constexpr uint32_t compact_http_slot = 3; inline constexpr uint32_t compact_alternate_service_slot = 91; inline constexpr uint32_t compact_plugin_http_peer_slot = 92; inline constexpr uint32_t compact_plugin_http_local_slot = 93; -inline constexpr uint32_t compact_alternate_p2p_slot = 94; +inline constexpr uint32_t compact_bios_p2p_slot = 94; +inline constexpr uint32_t compact_alternate_p2p_slot = 95; inline constexpr uint32_t compact_p2p_slot = 141; inline constexpr uint32_t compact_wallet_base_slot = 164; -inline constexpr uint32_t compact_wallet_slot = 165; inline constexpr uint32_t compact_ipv6_probe_slot = 171; inline constexpr uint32_t wallet_port_count = 5; @@ -40,6 +44,7 @@ enum class port_category { ship, state_history, bios_http, + bios_p2p, node_http, alternate_service, plugin_http_peer, @@ -57,11 +62,18 @@ inline uint32_t test_port_offset() { if(raw_offset == nullptr || raw_offset[0] == '\0') return 0; - try { - return static_cast(std::stoul(raw_offset)); - } catch(const std::exception& ex) { - throw std::runtime_error(std::string(test_port_offset_env_var) + " must be an unsigned integer: " + ex.what()); - } + std::string_view offset_view{raw_offset}; + if(offset_view.front() == '-') + throw std::runtime_error(std::string(test_port_offset_env_var) + " must be non-negative"); + + uint32_t offset = 0; + const auto* begin = offset_view.data(); + const auto* end = begin + offset_view.size(); + auto [ptr, ec] = std::from_chars(begin, end, offset); + if(ec != std::errc{} || ptr != end) + throw std::runtime_error(std::string(test_port_offset_env_var) + " must be an unsigned 32-bit integer"); + + return offset; } /** Return a deterministic port for a listener class and index in this test's shard. */ @@ -84,6 +96,10 @@ inline uint16_t get_port(port_category category, uint32_t index = 0) { slot_start = compact_bios_http_slot; unsharded_base_port = 8788; break; + case port_category::bios_p2p: + slot_start = compact_bios_p2p_slot; + unsharded_base_port = compact_bios_p2p_port; + break; case port_category::node_http: slot_start = compact_http_slot; slot_count = compact_http_last_port - compact_http_first_port + 1; @@ -117,7 +133,7 @@ inline uint16_t get_port(port_category category, uint32_t index = 0) { unsharded_base_port = 9899; break; case port_category::transaction_only: - slot_start = compact_wallet_slot + wallet_port_count - 1; + slot_start = compact_wallet_base_slot + wallet_port_count; slot_count = 2; unsharded_base_port = 9902; break; From dfd969b594764eb77f2e4f4110760d2d896ebdfa Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Thu, 11 Jun 2026 13:44:38 +0000 Subject: [PATCH 10/16] Use sharded ports in tests --- tests/TestHarness/Cluster.py | 4 +++- tests/cli_test.py | 5 +++-- tests/p2p_sync_throttle_test.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/TestHarness/Cluster.py b/tests/TestHarness/Cluster.py index bba0e7cdb4..ceb875ec9f 100644 --- a/tests/TestHarness/Cluster.py +++ b/tests/TestHarness/Cluster.py @@ -493,7 +493,9 @@ def connectGroup(group, producerNodes, bridgeNodes) : sysdcmd = launcher.construct_command_line(instance) nodeNum = instance.index - node = Node(self.host, self.port + nodeNum, nodeNum, Path(instance.data_dir_name), + # Bios port is shard-offset to PortBiosHttp; it no longer equals cluster.port+biosNodeId. + node_port = Cluster.__BiosPort if nodeNum == Node.biosNodeId else self.port + nodeNum + node = Node(self.host, node_port, nodeNum, Path(instance.data_dir_name), Path(instance.config_dir_name), sysdcmd, unstarted=instance.dont_start, launch_time=launcher.launch_time, walletMgr=self.walletMgr, nodeopVers=self.nodeopVers) node.keys = instance.keys diff --git a/tests/cli_test.py b/tests/cli_test.py index da9dc45baf..b5bfadbaf7 100755 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -347,7 +347,8 @@ def abi_file_with_nodeop_test(): tries = 30 nodeopPort = TestHelper.DEFAULT_PORT - while not Utils.arePortsAvailable(set(range(nodeopPort, nodeopPort + 1))): + p2pPort = Utils.getPort(Utils.PortBiosP2P) + while not Utils.arePortsAvailable({nodeopPort, p2pPort}): Utils.Print(f"ERROR: Another process is listening on nodeop test port {nodeopPort}. wait...") if tries == 0: assert False @@ -358,7 +359,7 @@ def abi_file_with_nodeop_test(): os.makedirs(data_dir, exist_ok=True) walletMgr = WalletMgr(True) walletMgr.launch() - cmd = f"./programs/nodeop/nodeop -e -p sysio --signature-provider wire-1,wire,wire,SYS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV,KEY:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 --plugin sysio::trace_api_plugin --trace-no-abis --plugin sysio::producer_plugin --plugin sysio::producer_api_plugin --plugin sysio::chain_api_plugin --plugin sysio::chain_plugin --plugin sysio::http_plugin --access-control-allow-origin=* --http-validate-host=false --http-server-address=localhost:{nodeopPort} --max-transaction-time=-1 --resource-monitor-not-shutdown-on-threshold-exceeded " + "--data-dir " + data_dir + " --config-dir " + data_dir + cmd = f"./programs/nodeop/nodeop -e -p sysio --signature-provider wire-1,wire,wire,SYS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV,KEY:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 --plugin sysio::trace_api_plugin --trace-no-abis --plugin sysio::producer_plugin --plugin sysio::producer_api_plugin --plugin sysio::chain_api_plugin --plugin sysio::chain_plugin --plugin sysio::http_plugin --access-control-allow-origin=* --http-validate-host=false --p2p-listen-endpoint=0.0.0.0:{p2pPort} --http-server-address=localhost:{nodeopPort} --max-transaction-time=-1 --resource-monitor-not-shutdown-on-threshold-exceeded " + "--data-dir " + data_dir + " --config-dir " + data_dir node = Node('localhost', nodeopPort, nodeId, data_dir=Path(data_dir), config_dir=Path(data_dir), cmd=shlex.split(cmd), launch_time=datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S'), walletMgr=walletMgr) if not node or not Utils.waitForBool(node.checkPulse, timeout=15): Utils.Print("ERROR: node doesn't appear to be running...") diff --git a/tests/p2p_sync_throttle_test.py b/tests/p2p_sync_throttle_test.py index 2b7b23b5df..8ea7c64b80 100755 --- a/tests/p2p_sync_throttle_test.py +++ b/tests/p2p_sync_throttle_test.py @@ -108,7 +108,7 @@ throttlingNode.cmd[i+1] = throttlingNode.cmd[i+1] + ':20KB/s' throttleListenIP, throttleListenPort = throttleListenAddr.split(':') throttlingNode.cmd.append('--p2p-listen-endpoint') - unThrottleListenAddr = f'{throttleListenIP}:{int(throttleListenPort)+100}' + unThrottleListenAddr = f'{throttleListenIP}:{Utils.getPort(Utils.PortAlternateP2P, 0)}' throttlingNode.cmd.append(f'{unThrottleListenAddr}:1TB/s') cluster.biosNode.kill(signal.SIGTERM) From 283bac9bf973d0430c8782502ff9b252ecae68a2 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Thu, 11 Jun 2026 17:00:22 +0000 Subject: [PATCH 11/16] Cache CTest cost data in CI --- .github/workflows/build.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bfdc8c41a1..91bdbca7d7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -184,6 +184,15 @@ jobs: touch build/debug_write_test 2>&1 || echo "Cannot write to build/" rm -f build/debug_write_test || true + - name: Restore CTest cost data + uses: actions/cache/restore@v5 + with: + # CTest uses this file to schedule higher-cost tests first when running with -j. + path: build/Testing/Temporary/CTestCostData.txt + key: ctest-cost-${{ matrix.platform }}-${{ github.sha }} + restore-keys: | + ctest-cost-${{ matrix.platform }}- + - name: Run Parallel Tests env: DOCKER_HOST: ${{ env.DOCKER_HOST }} @@ -204,6 +213,14 @@ jobs: echo "Running sharded NP/LR tests with ${test_jobs} jobs" ctest --output-on-failure -j "${test_jobs}" -L "(nonparallelizable_tests|long_running_tests)" --timeout 2700 + - name: Save CTest cost data + if: ${{ always() && hashFiles('build/Testing/Temporary/CTestCostData.txt') != '' }} + uses: actions/cache/save@v5 + with: + # Caches are immutable, so save each run under a unique key and restore by platform prefix. + path: build/Testing/Temporary/CTestCostData.txt + key: ctest-cost-${{ matrix.platform }}-${{ github.run_id }}-${{ github.run_attempt }} + - name: Upload core files from failed tests uses: actions/upload-artifact@v6 if: failure() From 3c932eece9f808d41aaa5d525228bec50a2d6ab3 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Thu, 11 Jun 2026 17:32:58 +0000 Subject: [PATCH 12/16] Remove obsolete build base workflow --- .github/workflows/build_base.yaml | 106 ------------------------------ tools/reproducible.Dockerfile | 2 +- 2 files changed, 1 insertion(+), 107 deletions(-) delete mode 100644 .github/workflows/build_base.yaml diff --git a/.github/workflows/build_base.yaml b/.github/workflows/build_base.yaml deleted file mode 100644 index 43c3ffcc23..0000000000 --- a/.github/workflows/build_base.yaml +++ /dev/null @@ -1,106 +0,0 @@ -name: "Build sysio" - -on: - workflow_call: - inputs: - platforms: - description: "Platforms definitions" - type: string - required: true - platform-list: - description: "Array of platforms" - type: string - required: true - secrets: - GH_TOKEN_DEV: - required: true - -permissions: - packages: read - contents: read - -defaults: - run: - shell: bash - -jobs: - Build: - name: Build wire-sysio - strategy: - fail-fast: false - matrix: - platform: ${{fromJSON(inputs.platform-list)}} - runs-on: - group: core-repo-group - container: - image: ${{ fromJSON(inputs.platforms)[matrix.platform].image }} - credentials: - username: dev-wire - password: ${{ secrets.GH_TOKEN_DEV }} - env: - CCACHE_MAXSIZE: "10G" - CCACHE_COMPRESS: "true" - CCACHE_COMPRESSLEVEL: "6" - steps: - - name: Allow safe directories - run: git config --global --add safe.directory '*' - - - uses: actions/checkout@v5 - with: - submodules: recursive - - - name: Restore vcpkg binary cache - uses: actions/cache@v5 - with: - path: ${{ github.workspace }}/vcpkg-binary-cache - key: vcpkg-binaries-${{ matrix.platform }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', '.github/vcpkg-triplets/**') }} - restore-keys: | - vcpkg-binaries-${{ matrix.platform }}- - - - name: Restore ccache - uses: actions/cache@v5 - with: - path: ${{ github.workspace }}/.ccache - key: ccache-${{ matrix.platform }}-${{ github.sha }} - restore-keys: | - ccache-${{ matrix.platform }}- - - - name: Build - id: build - env: - SYSIO_PLATFORM_NAME: ${{ matrix.platform }} - run: | - if [[ "${{ matrix.platform }}" == "gcc" ]]; then - build_jobs=8 - else - build_jobs=11 - fi - - bash .github/scripts/build-sysio.sh "$build_jobs" - - tar -pcz --exclude "*.o" build > build.tar.gz - - - name: Show vcpkg logs if failure - if: failure() - run: | - echo "$PWD" - echo "=== vcpkg-manifest-install.log ===" - cat build/vcpkg-manifest-install.log || true - echo "=== Other vcpkg logs ===" - find build -type f -name '*.log' | while read f; do - echo "----- $f -----" - tail -n 40 "$f" - done - -# # REQUIRED FOR ACT, LOCAL RUNS OF GITHUB ACTIONS -# - name: Installing nodejs -# run: | -# curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - -# sudo apt install -y nodejs - - - name: Upload builddir - uses: actions/upload-artifact@v6 - with: - name: ${{matrix.platform}}-build - path: build.tar.gz - compression-level: 0 diff --git a/tools/reproducible.Dockerfile b/tools/reproducible.Dockerfile index 8538de8826..bafa2528b7 100644 --- a/tools/reproducible.Dockerfile +++ b/tools/reproducible.Dockerfile @@ -113,7 +113,7 @@ FROM builder AS build ARG SYSIO_BUILD_JOBS -# Yuck: This places the source at the same location as wire-sysio's CI (build.yaml, build_base.yaml). Unfortunately this location only matches +# Yuck: This places the source at the same location as wire-sysio's CI. Unfortunately this location only matches # when build.yaml etc are being run from a repository named wire-sysio. COPY / /__w/sysio/sysio RUN cmake -S /__w/sysio/sysio -B build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -GNinja && \ From 532cc3cad9add9543ca908bffa87229dc3a28935 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Fri, 12 Jun 2026 11:20:59 -0500 Subject: [PATCH 13/16] Fix launcher option replacement guard --- tests/TestHarness/launcher.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/TestHarness/launcher.py b/tests/TestHarness/launcher.py index e66f0ea64a..cfe1a4f37c 100644 --- a/tests/TestHarness/launcher.py +++ b/tests/TestHarness/launcher.py @@ -16,6 +16,10 @@ block_dir = 'blocks' +def _is_command_option(token): + """Return whether a command-line token is an option name rather than an option value.""" + return token.startswith('--') + class EnhancedEncoder(json.JSONEncoder): def default(self, o): if is_dataclass(o): @@ -575,7 +579,7 @@ def construct_command_line(self, instance: nodeDefinition): if '-' in arg and arg not in repeatable: if arg in sysdcmd: i = sysdcmd.index(arg) - if sysdcmd[i+1] != '-': + if i + 1 < len(sysdcmd) and not _is_command_option(sysdcmd[i+1]): sysdcmd.pop(i+1) sysdcmd.pop(i) sysdcmd.extend(specificList) From d129ef3fd02abe7eb86f9cff5f60f7f84c3adec1 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Sun, 14 Jun 2026 09:50:26 -0500 Subject: [PATCH 14/16] Make snapshot tests port-sharded aware --- tests/snapshot_api_test.py | 7 +++++-- tests/snapshot_attest_test.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/snapshot_api_test.py b/tests/snapshot_api_test.py index 72bb59dd9f..697b0b21a6 100755 --- a/tests/snapshot_api_test.py +++ b/tests/snapshot_api_test.py @@ -71,9 +71,12 @@ def recordPresent(): pnodes=2 totalNodes=pnodes+1 +port=Utils.getPort(Utils.PortNodeHttp) +walletPort=Utils.getPort(Utils.PortWallet) -cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) -walletMgr=WalletMgr(True) +cluster=Cluster(port=port, walletPort=walletPort, unshared=args.unshared, + keepRunning=args.leave_running, keepLogs=args.keep_logs) +walletMgr=WalletMgr(True, nodeopPort=port, port=walletPort) try: TestHelper.printSystemInfo("BEGIN") diff --git a/tests/snapshot_attest_test.py b/tests/snapshot_attest_test.py index ee8625a9ca..61455f2c53 100755 --- a/tests/snapshot_attest_test.py +++ b/tests/snapshot_attest_test.py @@ -40,9 +40,12 @@ pnodes=2 totalNodes=pnodes+1 +port=Utils.getPort(Utils.PortNodeHttp) +walletPort=Utils.getPort(Utils.PortWallet) -cluster=Cluster(unshared=args.unshared, keepRunning=args.leave_running, keepLogs=args.keep_logs) -walletMgr=WalletMgr(True) +cluster=Cluster(port=port, walletPort=walletPort, unshared=args.unshared, + keepRunning=args.leave_running, keepLogs=args.keep_logs) +walletMgr=WalletMgr(True, nodeopPort=port, port=walletPort) try: TestHelper.printSystemInfo("BEGIN") From 98bc1543143e59a9eb32badd3b12d524a04bd270 Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Thu, 18 Jun 2026 15:06:30 +0000 Subject: [PATCH 15/16] Address port sharding review feedback --- .github/workflows/build.yaml | 6 ++ plugins/http_plugin/test/CMakeLists.txt | 2 +- plugins/http_plugin/test/unit_tests.cpp | 1 + tests/TestHarness/Cluster.py | 6 +- tests/TestHarness/WalletMgr.py | 9 +- tests/TestHarness/launcher.py | 6 +- tests/TestHarness/testUtils.py | 23 ++-- tests/nodeop_contrl_c_test.py | 2 - tests/p2p_multiple_listen_test.py | 8 +- tests/p2p_no_blocks_test.py | 4 +- tests/test_port_shard.hpp | 20 ++-- tests/trx_generator/CMakeLists.txt | 4 +- tests/trx_generator/trx_generator_tests.cpp | 111 +++++++++++++++++++- 13 files changed, 165 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 91bdbca7d7..dbb14f38ea 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -72,6 +72,8 @@ jobs: run_sharded_np_lr: true runs-on: group: core-repo-group + # Full-job ceiling for build, package, parallel tests, and sharded NP/LR tests whose per-test timeout is 45 minutes. + timeout-minutes: 360 container: image: ${{fromJSON(needs.platform-cache.outputs.platforms)[matrix.platform].image}} credentials: @@ -128,6 +130,7 @@ jobs: done - name: Debug environment and workspace + if: ${{ runner.debug == '1' }} run: | echo "=== SYSTEM INFO ===" whoami @@ -194,6 +197,7 @@ jobs: ctest-cost-${{ matrix.platform }}- - name: Run Parallel Tests + if: ${{ steps.build.outcome == 'success' && !cancelled() }} env: DOCKER_HOST: ${{ env.DOCKER_HOST }} DOCKER_CONTEXT: ${{ env.DOCKER_CONTEXT }} @@ -247,11 +251,13 @@ jobs: ../tools/tweak-deb.sh wire-sysio_*.deb - name: Install dev package + if: ${{ steps.build.outcome == 'success' && !cancelled() }} run: | apt-get update && apt-get upgrade -y apt-get install -y ./build/wire-sysio_*.deb - name: Upload dev package + if: ${{ steps.build.outcome == 'success' && !cancelled() }} uses: actions/upload-artifact@v6 with: name: wire-sysio-dev-${{matrix.platform}}-amd64 diff --git a/plugins/http_plugin/test/CMakeLists.txt b/plugins/http_plugin/test/CMakeLists.txt index f3be3b68bd..0b2b76f5af 100644 --- a/plugins/http_plugin/test/CMakeLists.txt +++ b/plugins/http_plugin/test/CMakeLists.txt @@ -7,7 +7,7 @@ target_link_libraries( http_plugin_unit_tests PRIVATE http_plugin ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) -target_include_directories( http_plugin_unit_tests PUBLIC +target_include_directories( http_plugin_unit_tests PRIVATE ${CMAKE_SOURCE_DIR}/plugins/http_plugin/include ${CMAKE_SOURCE_DIR}/tests ) diff --git a/plugins/http_plugin/test/unit_tests.cpp b/plugins/http_plugin/test/unit_tests.cpp index 8fb35dbbda..c0564748e8 100644 --- a/plugins/http_plugin/test/unit_tests.cpp +++ b/plugins/http_plugin/test/unit_tests.cpp @@ -39,6 +39,7 @@ constexpr uint32_t category_rw_index = 1; constexpr uint32_t category_ro_index = 2; constexpr uint32_t bytes_in_flight_index = 3; constexpr uint32_t requests_in_flight_index = 4; +// Keeps unsharded IPv6 probe coverage on the historical port 9999. constexpr uint32_t ipv6_probe_index = 2; /** Return the string form of a node HTTP port from this test's shard. */ diff --git a/tests/TestHarness/Cluster.py b/tests/TestHarness/Cluster.py index b36046b665..00181e2097 100644 --- a/tests/TestHarness/Cluster.py +++ b/tests/TestHarness/Cluster.py @@ -192,9 +192,9 @@ def _shutdownLaunchFailure(self): def _portsForLocalLaunch(self, totalNodes): """Return the local HTTP and P2P ports reserved by a cluster launch.""" - ports = set(range(self.port, self.port + totalNodes)) + ports = {Utils.getPort(Utils.PortNodeHttp, index) for index in range(totalNodes)} ports.add(Utils.getPort(Utils.PortBiosHttp)) - ports.update(range(self.p2pBasePort, self.p2pBasePort + totalNodes)) + ports.update(Utils.getPort(Utils.PortP2P, index) for index in range(totalNodes)) ports.add(Utils.getPort(Utils.PortBiosP2P)) return ports @@ -732,7 +732,7 @@ def populateWallet(self, accountsCount, wallet, accountNames: list=None, createP def getNodeP2pPort(self, nodeId: int): """Return the sharded P2P port for a non-BIOS node.""" - return self.p2pBasePort + nodeId + return Utils.getPort(Utils.PortP2P, nodeId) def getNodeP2pEndpoint(self, nodeId: int): """Return the sharded P2P endpoint for a non-BIOS node.""" diff --git a/tests/TestHarness/WalletMgr.py b/tests/TestHarness/WalletMgr.py index 5ed98608c7..f6037f500f 100644 --- a/tests/TestHarness/WalletMgr.py +++ b/tests/TestHarness/WalletMgr.py @@ -53,12 +53,15 @@ def isLocal(self): def findAvailablePort(self): """Find an available wallet port within this test's shard.""" - shardLimit=Utils.getPort(Utils.PortWallet, Utils.WalletPortCount - 1) if self.usesDefaultWalletPort else 65535 + offset=Utils.getTestPortOffset() + shardLimit=( + Utils.getPort(Utils.PortWallet, Utils.WalletPortCount - 1) + if offset > 0 and self.usesDefaultWalletPort else 65535) for i in range(WalletMgr.__MaxPort): port=self.port+i # pyright: ignore[reportOptionalOperand] - if Utils.getTestPortOffset() > 0 and port > shardLimit: + if port > shardLimit: break - if Utils.getTestPortOffset() == 0 and port > WalletMgr.__MaxPort: + if offset == 0 and port > WalletMgr.__MaxPort: port-=WalletMgr.__MaxPort if Utils.arePortsAvailable(port): return port diff --git a/tests/TestHarness/launcher.py b/tests/TestHarness/launcher.py index a61c45303b..471f6324e3 100644 --- a/tests/TestHarness/launcher.py +++ b/tests/TestHarness/launcher.py @@ -91,14 +91,16 @@ def http_bios_port(cls): @classmethod def create_p2p_port_generator(cls): + """Yield checked P2P ports from this test's shard.""" while True: - yield cls.base_p2p_port + cls.p2p_count + yield Utils.getPort(Utils.PortP2P, cls.p2p_count) cls.p2p_count += 1 @classmethod def create_http_port_generator(cls): + """Yield checked HTTP ports from this test's shard.""" while True: - yield cls.base_http_port + cls.http_count + yield Utils.getPort(Utils.PortNodeHttp, cls.http_count) cls.http_count += 1 @property diff --git a/tests/TestHarness/testUtils.py b/tests/TestHarness/testUtils.py index 2555b958cd..795d6177fd 100644 --- a/tests/TestHarness/testUtils.py +++ b/tests/TestHarness/testUtils.py @@ -110,6 +110,11 @@ class Utils: PortTransactionOnly="transaction_only" PortIpv6Probe="ipv6_probe" WalletPortCount=5 + TransactionOnlyPortCount=2 + Ipv6ProbePortCount=4 + PortWalletSlot=164 + PortTransactionOnlySlot=PortWalletSlot + WalletPortCount + PortIpv6ProbeSlot=PortTransactionOnlySlot + TransactionOnlyPortCount _testPortOffset=None _nodeopHelpOutput=None # Lock subprocess_results.log writes and cached nodeop option discovery across threads. @@ -122,13 +127,9 @@ def getTestPortOffset(): return Utils._testPortOffset rawOffset=os.environ.get(Utils.TestPortOffsetEnvVar, "0") - try: - offset=int(rawOffset) - except ValueError as ex: - raise RuntimeError(f"{Utils.TestPortOffsetEnvVar} must be an integer, got '{rawOffset}'") from ex - - if offset < 0: - raise RuntimeError(f"{Utils.TestPortOffsetEnvVar} must be non-negative, got {offset}") + if not re.fullmatch(r"[0-9]+", rawOffset): + raise RuntimeError(f"{Utils.TestPortOffsetEnvVar} must be an unsigned integer, got '{rawOffset}'") + offset=int(rawOffset) Utils._testPortOffset=offset return offset @@ -158,9 +159,9 @@ def getPort(port_category, index=0): Utils.PortBiosP2P: (94, 1), Utils.PortAlternateP2P: (95, 46), Utils.PortP2P: (141, 23), - Utils.PortWallet: (164, Utils.WalletPortCount), - Utils.PortTransactionOnly: (169, 2), - Utils.PortIpv6Probe: (171, 4), + Utils.PortWallet: (Utils.PortWalletSlot, Utils.WalletPortCount), + Utils.PortTransactionOnly: (Utils.PortTransactionOnlySlot, Utils.TransactionOnlyPortCount), + Utils.PortIpv6Probe: (Utils.PortIpv6ProbeSlot, Utils.Ipv6ProbePortCount), } if port_category not in slotRanges: @@ -173,6 +174,8 @@ def getPort(port_category, index=0): offset=Utils.getTestPortOffset() if offset == 0: + # The unsharded wallet and transaction-only defaults intentionally preserve + # historical manual-test ports, including their legacy overlap at 9902/9903. defaultPorts={ Utils.PortShip: 7899, Utils.PortStateHistory: 8080, diff --git a/tests/nodeop_contrl_c_test.py b/tests/nodeop_contrl_c_test.py index 2d25976ea8..c78454b388 100755 --- a/tests/nodeop_contrl_c_test.py +++ b/tests/nodeop_contrl_c_test.py @@ -29,8 +29,6 @@ activateIF=args.activate_if walletPort=args.wallet_port walletMgr=WalletMgr(True, port=walletPort) -producerEndpoint = f'127.0.0.1:{cluster.port}' -httpServerAddress = f'127.0.0.1:{cluster.port + 1}' testSuccessful=False trxGenLauncher=None diff --git a/tests/p2p_multiple_listen_test.py b/tests/p2p_multiple_listen_test.py index e13871ef91..b9293c2152 100755 --- a/tests/p2p_multiple_listen_test.py +++ b/tests/p2p_multiple_listen_test.py @@ -69,8 +69,8 @@ def getOpenPeerAddress(node): # Be sure all nodes start out connected (bios node omitted from diagram for brevity) # node00 node01 node02 node03 node04 - # localhost:9876 -> localhost:9877 -> localhost:9878 -> localhost:9879 -> localhost:9880 - # localhost:9779 ^ | | + # default p2p -> default p2p -> default p2p -> default p2p -> default p2p + # alternate p2p ^ | | # ^ +---------------------------+ | # +------------------------------------------------------------------------+ cluster.waitOnClusterSync(blockAdvancing=5) @@ -82,8 +82,8 @@ def getOpenPeerAddress(node): cluster.getNode(3).kill(signal.SIGTERM) # Be sure all remaining nodes continue to sync via the two listen ports on node 00 # node00 node01 node02 node03 node04 - # localhost:9876 offline localhost:9878 offline localhost:9880 - # localhost:9779 ^ | | + # default p2p offline default p2p offline default p2p + # alternate p2p ^ | | # ^ +---------------------------+ | # +------------------------------------------------------------------------+ cluster.waitOnClusterSync(blockAdvancing=5) diff --git a/tests/p2p_no_blocks_test.py b/tests/p2p_no_blocks_test.py index 30359a226e..9c01cd2cbe 100755 --- a/tests/p2p_no_blocks_test.py +++ b/tests/p2p_no_blocks_test.py @@ -49,8 +49,8 @@ Print("Stand up cluster") # Custom topology: # prodNode00 <-> nonProdNode01 - # -> noBlocks02 :9902 (p2p-listen-address with trx only) (speculative mode) - # -> noBlocks03 :9903 (p2p-listen-address with trx only) + # -> noBlocks02 : trx-only listener (speculative mode) + # -> noBlocks03 : trx-only listener # # 01-nonProdNode connects to 02 & 03, but 02 & 03 do not connect to 01 so they will not receive any blocks # 02 & 03 are connected to the bios node to get blocks until bios node is killed. diff --git a/tests/test_port_shard.hpp b/tests/test_port_shard.hpp index df712219ef..6ac51dd6d1 100644 --- a/tests/test_port_shard.hpp +++ b/tests/test_port_shard.hpp @@ -12,7 +12,7 @@ namespace sysio::testing { inline constexpr const char* test_port_offset_env_var = "SYSIO_TEST_PORT_OFFSET"; -inline constexpr uint16_t default_state_history_port = 8080; +inline constexpr uint32_t default_state_history_port = 8080; inline constexpr uint32_t compact_shard_anchor_port = 8888; inline constexpr uint32_t compact_http_first_port = 8888; inline constexpr uint32_t compact_http_last_port = 8975; @@ -21,8 +21,11 @@ inline constexpr uint32_t compact_alternate_first_port = 9777; inline constexpr uint32_t compact_alternate_last_port = 9822; inline constexpr uint32_t compact_p2p_first_port = 9876; inline constexpr uint32_t compact_p2p_last_port = 9898; -inline constexpr uint32_t compact_wallet_first_port = 9900; +inline constexpr uint32_t compact_wallet_first_port = 9899; inline constexpr uint32_t compact_wallet_last_port = 9903; +// These unsharded transaction-only defaults intentionally preserve the legacy overlap with wallet ports 9902/9903. +inline constexpr uint32_t compact_transaction_only_first_port = 9902; +inline constexpr uint32_t compact_transaction_only_last_port = 9903; inline constexpr uint32_t compact_ipv6_probe_first_port = 9997; inline constexpr uint32_t compact_ipv6_probe_last_port = 10000; inline constexpr uint32_t compact_ship_slot = 0; @@ -36,8 +39,11 @@ inline constexpr uint32_t compact_bios_p2p_slot = 94; inline constexpr uint32_t compact_alternate_p2p_slot = 95; inline constexpr uint32_t compact_p2p_slot = 141; inline constexpr uint32_t compact_wallet_base_slot = 164; -inline constexpr uint32_t compact_ipv6_probe_slot = 171; inline constexpr uint32_t wallet_port_count = 5; +inline constexpr uint32_t transaction_only_port_count = + compact_transaction_only_last_port - compact_transaction_only_first_port + 1; +inline constexpr uint32_t compact_transaction_only_slot = compact_wallet_base_slot + wallet_port_count; +inline constexpr uint32_t compact_ipv6_probe_slot = compact_transaction_only_slot + transaction_only_port_count; /** Logical listener classes inside a test's compact port shard. */ enum class port_category { @@ -130,12 +136,12 @@ inline uint16_t get_port(port_category category, uint32_t index = 0) { case port_category::wallet: slot_start = compact_wallet_base_slot; slot_count = wallet_port_count; - unsharded_base_port = 9899; + unsharded_base_port = compact_wallet_first_port; break; case port_category::transaction_only: - slot_start = compact_wallet_base_slot + wallet_port_count; - slot_count = 2; - unsharded_base_port = 9902; + slot_start = compact_transaction_only_slot; + slot_count = transaction_only_port_count; + unsharded_base_port = compact_transaction_only_first_port; break; case port_category::ipv6_probe: slot_start = compact_ipv6_probe_slot; diff --git a/tests/trx_generator/CMakeLists.txt b/tests/trx_generator/CMakeLists.txt index ee72c7398f..a0007db5d5 100644 --- a/tests/trx_generator/CMakeLists.txt +++ b/tests/trx_generator/CMakeLists.txt @@ -1,12 +1,12 @@ add_executable(trx_generator main.cpp trx_generator.cpp trx_provider.cpp) -target_include_directories(trx_generator PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} +target_include_directories(trx_generator PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/plugins/net_plugin/include) target_link_libraries(trx_generator PRIVATE sysio_chain fc Boost::program_options ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS}) add_executable(trx_generator_tests trx_generator_tests.cpp trx_provider.cpp trx_generator.cpp) target_link_libraries(trx_generator_tests PRIVATE sysio_chain fc Boost::program_options ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS}) -target_include_directories(trx_generator_tests PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} +target_include_directories(trx_generator_tests PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_SOURCE_DIR}/plugins/net_plugin/include) add_np_test(NAME trx_generator_tests COMMAND trx_generator_tests) diff --git a/tests/trx_generator/trx_generator_tests.cpp b/tests/trx_generator/trx_generator_tests.cpp index ca643b5bb8..2e101d63e0 100644 --- a/tests/trx_generator/trx_generator_tests.cpp +++ b/tests/trx_generator/trx_generator_tests.cpp @@ -7,12 +7,73 @@ #define BOOST_TEST_MODULE trx_generator_tests #include +#include +#include +#include +#include +#include + using namespace sysio; using namespace sysio::testing; using namespace std::literals::string_literals; static const char* api_name = "/v1/chain/test"; +namespace { +/** Restore SYSIO_TEST_PORT_OFFSET after tests that exercise environment parsing. */ +class test_port_offset_env_guard { + public: + test_port_offset_env_guard() { + if(const char* value = std::getenv(test_port_offset_env_var)) + _previous = value; + } + + ~test_port_offset_env_guard() { + if(_previous) + setenv(test_port_offset_env_var, _previous->c_str(), 1); + else + unsetenv(test_port_offset_env_var); + } + + /** Set the current process test port offset for this scoped test. */ + void set(const char* value) const { setenv(test_port_offset_env_var, value, 1); } + + /** Clear the current process test port offset for this scoped test. */ + void clear() const { unsetenv(test_port_offset_env_var); } + + private: + std::optional _previous; +}; + +struct expected_port { + port_category category; + uint32_t slot; + uint32_t count; + uint16_t unsharded_base; +}; + +constexpr std::array expected_ports{ + expected_port{port_category::ship, compact_ship_slot, 1, 7899}, + expected_port{port_category::state_history, compact_state_history_slot, 1, default_state_history_port}, + expected_port{port_category::bios_http, compact_bios_http_slot, 1, 8788}, + expected_port{port_category::bios_p2p, compact_bios_p2p_slot, 1, compact_bios_p2p_port}, + expected_port{port_category::node_http, compact_http_slot, compact_http_last_port - compact_http_first_port + 1, + compact_http_first_port}, + expected_port{port_category::alternate_service, compact_alternate_service_slot, 1, 8976}, + expected_port{port_category::plugin_http_peer, compact_plugin_http_peer_slot, 1, 9009}, + expected_port{port_category::plugin_http_local, compact_plugin_http_local_slot, 1, 9011}, + expected_port{port_category::alternate_p2p, compact_alternate_p2p_slot, + compact_alternate_last_port - compact_alternate_first_port + 1, compact_alternate_first_port}, + expected_port{port_category::p2p, compact_p2p_slot, compact_p2p_last_port - compact_p2p_first_port + 1, + compact_p2p_first_port}, + expected_port{port_category::wallet, compact_wallet_base_slot, wallet_port_count, compact_wallet_first_port}, + expected_port{port_category::transaction_only, compact_transaction_only_slot, transaction_only_port_count, + compact_transaction_only_first_port}, + expected_port{port_category::ipv6_probe, compact_ipv6_probe_slot, + compact_ipv6_probe_last_port - compact_ipv6_probe_first_port + 1, compact_ipv6_probe_first_port}}; + +} // namespace + namespace http = boost::beast::http; struct echo_server_impl : rest::simple_server { @@ -367,11 +428,59 @@ BOOST_AUTO_TEST_CASE(tps_cant_keep_up_monitored) BOOST_REQUIRE_LT(generator->_calls.size(), expected_trxs); } +BOOST_AUTO_TEST_CASE(test_port_shard_unsharded_defaults) +{ + test_port_offset_env_guard env; + env.clear(); + + for(const auto& expected : expected_ports) { + BOOST_REQUIRE_EQUAL(get_port(expected.category), expected.unsharded_base); + BOOST_REQUIRE_EQUAL(get_port(expected.category, expected.count - 1), + expected.unsharded_base + expected.count - 1); + } +} + +BOOST_AUTO_TEST_CASE(test_port_shard_sharded_offsets) +{ + test_port_offset_env_guard env; + env.set("100"); + + constexpr uint32_t offset = 100; + for(const auto& expected : expected_ports) { + BOOST_REQUIRE_EQUAL(get_port(expected.category), compact_shard_anchor_port + offset + expected.slot); + BOOST_REQUIRE_EQUAL(get_port(expected.category, expected.count - 1), + compact_shard_anchor_port + offset + expected.slot + expected.count - 1); + } +} + +BOOST_AUTO_TEST_CASE(test_port_shard_rejects_invalid_indices) +{ + test_port_offset_env_guard env; + env.set("100"); + + for(const auto& expected : expected_ports) { + BOOST_REQUIRE_THROW(get_port(expected.category, expected.count), std::runtime_error); + } +} + +BOOST_AUTO_TEST_CASE(test_port_shard_rejects_invalid_offsets) +{ + test_port_offset_env_guard env; + + for(const char* value : {"-1", "+1", " 1", "1 ", "1_000"}) { + env.set(value); + BOOST_REQUIRE_THROW(test_port_offset(), std::runtime_error); + } + + env.set("60000"); + BOOST_REQUIRE_THROW(get_port(port_category::ipv6_probe, 3), std::runtime_error); +} + BOOST_AUTO_TEST_CASE(trx_generator_constructor) { trx_generator_base_config tg_config{1, chain::chain_id_type("999"), chain::name("sysio"), fc::seconds(3600), fc::variant("00000062989f69fd251df3e0b274c3364ffc2f4fce73de3f1c7b5e11a4c92f21").as(), ".", true}; - provider_base_config p_config{"p2p", "127.0.0.1", 9876}; + provider_base_config p_config{"p2p", "127.0.0.1", get_port(port_category::p2p)}; const std::string abi_file = "../../contracts/sysio.token/sysio.token.abi"; const std::string actions_data = "[{\"actionAuthAcct\": \"testacct1\",\"actionName\": \"transfer\",\"authorization\": {\"actor\": \"testacct1\",\"permission\": \"active\"}," "\"actionData\": {\"from\": \"testacct1\",\"to\": \"testacct2\",\"quantity\": \"0.0001 CUR\",\"memo\": \"transaction specified\"}}]"; From f0509d9e8c967054cac881fec8872b047d6e3dfc Mon Sep 17 00:00:00 2001 From: Huang-Ming Huang Date: Thu, 18 Jun 2026 16:27:37 +0000 Subject: [PATCH 16/16] Stabilize liveness test after producer shutdown --- tests/liveness_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/liveness_test.py b/tests/liveness_test.py index 34c939450c..f913f8cc01 100755 --- a/tests/liveness_test.py +++ b/tests/liveness_test.py @@ -70,7 +70,7 @@ # verify head still advances but not LIB on node B assert prodB.waitForNextBlock(), "Head should continue to advance on node B without node A" - assert not prodB.waitForLibToAdvance(10), "LIB should not advance on node B without node A" + assert prodB.waitForLibNotToAdvance(10), "LIB should stop advancing on node B without node A" # relaunch node A so that quorum can be met Print("Relaunching node A to make quorum")