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 d06959871c..dbb14f38ea 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -48,125 +48,89 @@ 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 + # 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: 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: - - uses: actions/checkout@v5 + - name: Allow safe directories + run: git config --global --add safe.directory '*' - - name: Download builddir - uses: actions/download-artifact@v7 + - uses: actions/checkout@v5 with: - name: ${{matrix.platform}}-build - - - name: Build packages - run: | - tar xzf build.tar.gz - 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 + submodules: recursive - - name: Upload dev package - uses: actions/upload-artifact@v6 + - name: Restore vcpkg binary cache + uses: actions/cache@v5 with: - name: wire-sysio-dev-${{matrix.platform}}-amd64 - path: build/wire-sysio_*.deb - - 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 + 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 }}- - steps: - - name: Ensure writable workspace + - name: Build + id: build + env: + SYSIO_PLATFORM_NAME: ${{ matrix.platform }} 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" + bash .github/scripts/build-sysio.sh "${{ matrix.build_jobs }}" - - uses: actions/checkout@v5 - - - name: Download builddir - uses: actions/download-artifact@v7 - with: - name: ${{matrix.cfg.builddir}}-build + - 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 + if: ${{ runner.debug == '1' }} run: | echo "=== SYSTEM INFO ===" whoami @@ -220,281 +184,116 @@ 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: 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 + if: ${{ steps.build.outcome == 'success' && !cancelled() }} env: 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: ${{ 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 + + - 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() with: - name: ${{matrix.cfg.name}}-tests-logs + name: ${{matrix.platform}}-tests-logs if-no-files-found: warn path: | /cores build/Testing/Temporary/ build/TestLogs/ + 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 - 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 + - name: Build packages + if: ${{ steps.build.outcome == 'success' && !cancelled() }} 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 + cd build + cpack + ../tools/tweak-deb.sh wire-sysio_*.deb - - 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() + - name: Install dev package + if: ${{ steps.build.outcome == 'success' && !cancelled() }} 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" + apt-get update && apt-get upgrade -y + apt-get install -y ./build/wire-sysio_*.deb - - name: Upload logs from failed tests - if: failure() + - name: Upload dev package + if: ${{ steps.build.outcome == 'success' && !cancelled() }} 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 + 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: 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() + - name: Setup wire-cdt and wire-system-contracts versions + id: versions + env: + GH_TOKEN: ${{secrets.GH_TOKEN_DEV}} 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 + 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 - - 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] - # 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) + 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, np-tests, lr-tests ] + needs: [ build-test-package ] if: always() runs-on: ubuntu-latest steps: @@ -505,8 +304,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: "build-test-package:${{ needs.build-test-package.result }}" github-context: ${{ toJSON(github) }} - - if: needs.tests.result != 'success' || needs.np-tests.result != 'success' || needs.lr-tests.result != 'success' + - if: needs.build-test-package.result != 'success' run: false diff --git a/.github/workflows/build_base.yaml b/.github/workflows/build_base.yaml deleted file mode 100644 index 9f618b2b8e..0000000000 --- a/.github/workflows/build_base.yaml +++ /dev/null @@ -1,143 +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 - 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 - else - cmake --build build -- -j 11 - fi - - echo "=== ccache statistics ===" - ccache -s || true - - 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/cmake/test-helpers.cmake b/cmake/test-helpers.cmake index a7217f62f2..8a5ada9123 100644 --- a/cmake/test-helpers.cmake +++ b/cmake/test-helpers.cmake @@ -1,5 +1,92 @@ +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) + +# 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_ANCHOR} + ${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) + if(NOT next_offset) + set(next_offset "${SYSIO_TEST_PORT_OFFSET_START}") + endif() + + while(1) + 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() + + test_port_offset_overlaps_forbidden_port(overlaps_forbidden_port "${next_offset}") + if(NOT overlaps_forbidden_port) + break() + endif() + + math(EXPR next_offset "${next_offset} + ${SYSIO_TEST_PORT_OFFSET_STRIDE}") + 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) +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" "NAME;COST;TIMEOUT;PORT_OFFSET" "COMMAND;ENVIRONMENT") add_test(NAME "${arg_NAME}" COMMAND ${arg_COMMAND} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") @@ -9,6 +96,18 @@ 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_PORT_OFFSET) + next_test_port_offset(test_port_offset) + endif() + set(test_environment ${arg_ENVIRONMENT}) + if(DEFINED 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() function(add_p_test) @@ -25,13 +124,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_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 new file mode 100644 index 0000000000..2975e8fb3e --- /dev/null +++ b/docs/port-sharding-design.md @@ -0,0 +1,264 @@ +# Port Sharding Test Design + +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 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 helpers return the historical local ports such as `8888`, `9876`, +and `9899`. This keeps direct manual test runs familiar. + +## 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 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. 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: + +```text +100, 292, 484, 676, 868, ... +``` + +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 +``` + +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. + +## 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: + +- Python: `Utils.getPort(Utils.PortStateHistory, index)` +- C++: `sysio::testing::get_port(sysio::testing::port_category::state_history, index)` + +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. + +Current category slots are: + +| 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 | +| `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 | +| `ipv6_probe` | `9997` | `171..174` | `4` | IPv6 probe listeners | + +The current highest used slot is `174`, leaving slots `175..191` reserved for future categories without changing the +CTest stride. + +## Why Categories Avoid Collisions + +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. + +For example, with offsets `100` and `292`: + +| Category/index | Offset `100` | Offset `292` | +|---|---:|---:| +| `state_history` | `8989` | `9181` | +| `bios_http` | `8990` | `9182` | +| `node_http[0]` | `8991` | `9183` | +| `bios_p2p` | `9082` | `9274` | +| `alternate_p2p[0]` | `9083` | `9275` | +| `p2p[0]` | `9129` | `9321` | +| `wallet[0]` | `9152` | `9344` | +| `transaction_only[0]` | `9157` | `9349` | + +The first shard's active category range ends before the next shard starts. + +## Shared State Considerations + +Port sharding only solves listener-port conflicts. Some tests also need non-port shared state kept local to the test: + +- 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. + +## Timeout Considerations + +`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 + +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 `192`: + +```bash +SYSIO_TEST_PORT_OFFSET=100 python3 tests/nodeop_run_test.py +SYSIO_TEST_PORT_OFFSET=292 python3 tests/http_plugin_test.py +``` + +New test endpoints should use one of these patterns: + +- `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. +- `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 +the current test's 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 conservative compact shard ceiling: + +```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_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 +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 +``` + +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 105 offsets with no duplicates: + +```text +min_offset = 100 +max_offset = 20068 +max_hot_port = 29147 +``` + +Targeted validation after the latest fixes: + +```text +p2p_multiple_listen_test: passed +nodeop_irreversible_mode_lr_test: passed +ship_test_unix: passed +``` + +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 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 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/plugins/http_plugin/test/CMakeLists.txt b/plugins/http_plugin/test/CMakeLists.txt index 2584588b65..0b2b76f5af 100644 --- a/plugins/http_plugin/test/CMakeLists.txt +++ b/plugins/http_plugin/test/CMakeLists.txt @@ -7,7 +7,8 @@ target_link_libraries( http_plugin_unit_tests PRIVATE http_plugin ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) -target_include_directories( http_plugin_unit_tests PUBLIC - ${CMAKE_SOURCE_DIR}/plugins/http_plugin/include ) +target_include_directories( http_plugin_unit_tests PRIVATE + ${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..c0564748e8 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,26 @@ namespace http = beast::http; // from namespace net = boost::asio; // from using tcp = net::ip::tcp; // from +namespace { +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; +// 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. */ +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_http_endpoint(const std::string& host, uint32_t index) { + return host + ":" + test_http_port(index); +} +} // namespace + // ------------------------------------------------------------------------- // this class handles some basic http requests. // ------------------------------------------------------------------------- @@ -300,9 +322,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::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 = "/"}); @@ -392,33 +414,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_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_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: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 +496,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_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; + 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 +515,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 +565,15 @@ 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::get_port(sysio::testing::port_category::ipv6_probe, + ipv6_probe_index)}); return true; } catch (...) { return false; @@ -538,27 +581,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 +613,25 @@ bool on_loopback(std::initializer_list args){ } BOOST_AUTO_TEST_CASE(test_on_loopback) { + 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", "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_http_endpoint("127.0.0.1", bytes_in_flight_index); + const std::string server_address = "--http-server-address=" + endpoint; + const std::string port = test_http_port(bytes_in_flight_index); + 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 +654,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 +728,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_http_endpoint("127.0.0.1", requests_in_flight_index); + const std::string server_address = "--http-server-address=" + endpoint; + const std::string port = test_http_port(requests_in_flight_index); + 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 +750,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 +808,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 ed79850e5c..448a28e6c2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -122,12 +122,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) @@ -136,11 +130,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}) -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_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) +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) add_subdirectory( TestHarness ) add_subdirectory( trx_generator ) @@ -152,77 +146,77 @@ 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}) -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_log_util_test COMMAND tests/ship_log_util_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_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 snapshot_attest_test COMMAND tests/snapshot_attest_test.py -v ${UNSHARE}) -add_np_test(NAME snapshot_api_test COMMAND tests/snapshot_api_test.py -v ${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) +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_log_util_test COMMAND tests/ship_log_util_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) +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 snapshot_attest_test COMMAND tests/snapshot_attest_test.py -v) +add_np_test(NAME snapshot_api_test COMMAND tests/snapshot_api_test.py -v) +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}) -add_np_test(NAME p2p_no_listen_test COMMAND tests/p2p_no_listen_test.py -v ${UNSHARE}) -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_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) +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) +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 #add_np_test(NAME p2p_high_latency_test COMMAND tests/p2p_high_latency_test.py -v) @@ -230,84 +224,86 @@ 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) -add_np_test(NAME nodeop_contrl_c_test COMMAND tests/nodeop_contrl_c_test.py -v --wallet-port 9901 ${UNSHARE}) +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 --wallet-port 9904 ${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}) +# 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 ${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 --wallet-port 9905 ${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) +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) 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) -set_tests_properties(plugin_http_category_api_test PROPERTIES ENVIRONMENT "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) 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_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 ${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_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 ${UNSHARE}) -add_lr_test(NAME auto_bp_gossip_peering_test COMMAND tests/auto_bp_gossip_peering_test.py -v ${UNSHARE}) +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) # Fast, cluster-free unit test for the PerformanceHarness post-test trx extraction logic. add_p_test(NAME performance_test_basic_query_test COMMAND tests/PerformanceHarness/performance_test_basic_query_test.py) -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 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) diff --git a/tests/PerformanceHarness/performance_test_basic.py b/tests/PerformanceHarness/performance_test_basic.py index 0224da6365..728ca6646d 100755 --- a/tests/PerformanceHarness/performance_test_basic.py +++ b/tests/PerformanceHarness/performance_test_basic.py @@ -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 06fe22b37b..00181e2097 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.getPort(Utils.PortBiosHttp) __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.getPort(Utils.PortNodeHttp) if port is None else port + self.p2pBasePort=Utils.getPort(Utils.PortP2P) self.walletHost=walletHost - self.walletPort=walletPort + self.walletPort=Utils.getPort(Utils.PortWallet) 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 = {Utils.getPort(Utils.PortNodeHttp, index) for index in range(totalNodes)} + ports.add(Utils.getPort(Utils.PortBiosHttp)) + ports.update(Utils.getPort(Utils.PortP2P, index) for index in range(totalNodes)) + 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, 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} ' @@ -463,7 +491,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 @@ -485,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.") @@ -504,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"] @@ -701,7 +731,20 @@ def populateWallet(self, accountsCount, wallet, accountNames: list=None, createP return True def getNodeP2pPort(self, nodeId: int): - return self.p2pBasePort + nodeId + """Return the sharded P2P port for a non-BIOS node.""" + return Utils.getPort(Utils.PortP2P, 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}:{Utils.getPort(Utils.PortBiosP2P)}" + + 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): diff --git a/tests/TestHarness/Node.py b/tests/TestHarness/Node.py index 1e5afa2853..82dba6d79a 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)) @@ -499,12 +537,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 @@ -512,6 +552,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..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=8888 - DEFAULT_WALLET_PORT=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 ef7b713199..2e6f811931 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.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 aaa3e768dc..f6037f500f 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.getPort(Utils.PortNodeHttp) if nodeopPort is None else nodeopPort self.nodeopHost=nodeopHost - self.port=port + self.port=Utils.getPort(Utils.PortWallet) 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,16 @@ 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.""" + 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 port > WalletMgr.__MaxPort: + if port > shardLimit: + break + if offset == 0 and port > WalletMgr.__MaxPort: port-=WalletMgr.__MaxPort if Utils.arePortsAvailable(port): return port @@ -317,9 +326,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 67bb9e1ee3..471f6324e3 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): @@ -50,8 +54,10 @@ 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.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,22 +81,26 @@ 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): + """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 @@ -570,7 +580,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) diff --git a/tests/TestHarness/testUtils.py b/tests/TestHarness/testUtils.py index 2af1e5bd16..795d6177fd 100644 --- a/tests/TestHarness/testUtils.py +++ b/tests/TestHarness/testUtils.py @@ -95,9 +95,138 @@ class Utils: ConfigDir=f"{DataPath}/" TimeFmt='%Y-%m-%dT%H:%M:%S.%f' - # Lock subprocess_results.log writes across threads. + TestPortOffsetEnvVar="SYSIO_TEST_PORT_OFFSET" + PortShip="ship" + PortStateHistory="state_history" + PortBiosHttp="bios_http" + PortBiosP2P="bios_p2p" + 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 + 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. _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") + 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 + + @staticmethod + def getPort(port_category, index=0): + """Return a deterministic port from this test's compact port shard. + + 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_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.PortBiosP2P: (94, 1), + Utils.PortAlternateP2P: (95, 46), + Utils.PortP2P: (141, 23), + Utils.PortWallet: (Utils.PortWalletSlot, Utils.WalletPortCount), + Utils.PortTransactionOnly: (Utils.PortTransactionOnlySlot, Utils.TransactionOnlyPortCount), + Utils.PortIpv6Probe: (Utils.PortIpv6ProbeSlot, Utils.Ipv6ProbePortCount), + } + + 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: + # 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, + Utils.PortBiosHttp: 8788, + Utils.PortBiosP2P: 9776, + Utils.PortNodeHttp: 8888, + Utils.PortAlternateService: 8976, + Utils.PortPluginHttpPeer: 9009, + Utils.PortPluginHttpLocal: 9011, + Utils.PortAlternateP2P: 9777, + Utils.PortP2P: 9876, + Utils.PortWallet: 9899, + Utils.PortTransactionOnly: 9902, + Utils.PortIpv6Probe: 9997, + } + shiftedPort=defaultPorts[port_category] + index + else: + shiftedPort=8888 + offset + slotStart + index + + if shiftedPort < 1 or shiftedPort > 65535: + raise RuntimeError( + f"Port category '{port_category}' index {index} shifted by " + f"{Utils.TestPortOffsetEnvVar}={Utils.getTestPortOffset()} " + f"produces invalid port {shiftedPort}") + return shiftedPort + + @staticmethod + def nodeopSupportsOption(option): + """Return whether the built nodeop binary advertises the given command-line option.""" + with Utils._check_output_lock: + if Utils._nodeopHelpOutput is None: + try: + result=subprocess.run( + [Utils.SysServerPath, "--help"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + check=False) + except OSError as ex: + raise RuntimeError( + f"Unable to inspect nodeop options with '{Utils.SysServerPath} --help': {ex}" + ) from ex + if result.returncode != 0 or not result.stdout.strip(): + raise RuntimeError( + f"Unable to inspect nodeop options with '{Utils.SysServerPath} --help': " + f"return code {result.returncode}, output: {result.stdout}" + ) + Utils._nodeopHelpOutput=result.stdout + + return re.search(rf"^\s*{re.escape(option)}([=\s]|$)", Utils._nodeopHelpOutput, re.MULTILINE) is not None + @staticmethod def _wasmCapabilities(): """Return CMake-generated WASM capabilities.""" @@ -468,7 +597,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 e40ef07dd0..a72e2c283c 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,10 @@ 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 + 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 tries -= 1 @@ -353,8 +359,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 --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 --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...") assert False, "node doesn't appear to be running" @@ -424,7 +430,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/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") diff --git a/tests/nodeop_contrl_c_test.py b/tests/nodeop_contrl_c_test.py index 8153a326cf..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 = '127.0.0.1:8888' -httpServerAddress = '127.0.0.1:8889' testSuccessful=False trxGenLauncher=None diff --git a/tests/nodeop_forked_chain_test.py b/tests/nodeop_forked_chain_test.py index 7adb3976f1..3a9bcf7054 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.getPort(Utils.PortStateHistory)}" + ) # 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.getPort(Utils.PortStateHistory)} " + 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..b9293c2152 100755 --- a/tests/p2p_multiple_listen_test.py +++ b/tests/p2p_multiple_listen_test.py @@ -13,6 +13,9 @@ 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' args=TestHelper.parse_args({"-p","-n","-d","--keep-logs" ,"--activate-if","--dump-error-details","-v" @@ -30,6 +33,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,10 +54,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.getPort(Utils.PortAlternateP2P, 2)}" + alternatePeerEndpoint = f"localhost:{Utils.getPort(Utils.PortAlternateP2P, 2)}" 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 {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', } if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo='line', delay=delay, activateIF=activateIF, specificExtraNodeopArgs=specificArgs) is False: @@ -49,8 +69,8 @@ # 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) @@ -62,8 +82,8 @@ 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) @@ -73,30 +93,22 @@ 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') - 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 8b0376fa8e..9c01cd2cbe 100755 --- a/tests/p2p_no_blocks_test.py +++ b/tests/p2p_no_blocks_test.py @@ -49,22 +49,24 @@ 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. # specificExtraNodeopArgs = {} + 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: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..626ade37a8 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.getPort(Utils.PortP2P) + 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/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) diff --git a/tests/plugin_http_api_test.py b/tests/plugin_http_api_test.py index 3882e15d29..7472a5f0a1 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.getPort(Utils.PortPluginHttpLocal)}" 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.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", "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 --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.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) @@ -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.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) @@ -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 05fac46e27..dbc5a6c5fa 100755 --- a/tests/resource_monitor_plugin_test.py +++ b/tests/resource_monitor_plugin_test.py @@ -184,9 +184,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", ["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", ["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 --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 --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..af12cd576a 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.getPort(Utils.PortStateHistory)} " + "--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..cea56c3d9a 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.getPort(Utils.PortStateHistory)}" 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..d28228469b 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.getPort(Utils.PortStateHistory)} " + "--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.getPort(Utils.PortStateHistory)}" + 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..02bd1d29bd 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.getPort(Utils.PortStateHistory)} " + "--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 7cd73b42cd..fe30a5e0c0 100755 --- a/tests/ship_streamer_test.py +++ b/tests/ship_streamer_test.py @@ -71,7 +71,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.getPort(Utils.PortStateHistory)} " + "--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 @@ -145,7 +151,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.getPort(Utils.PortStateHistory)}" + + 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}") @@ -326,7 +342,7 @@ def runShipClients(cmd, phase, startBlockNum, endBlockNum, expectedField): 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}") @@ -346,7 +362,7 @@ def runShipClients(cmd, phase, startBlockNum, endBlockNum, expectedField): 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..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") @@ -58,10 +64,18 @@ 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.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, @@ -81,7 +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.getPort(Utils.PortStateHistory)) if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) clients = [] files = [] @@ -118,7 +134,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 +155,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)) @@ -194,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/snapshot_api_test.py b/tests/snapshot_api_test.py index 93600df0d9..22005ebb9c 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 21257be2de..a1470f40f1 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") 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..6ac51dd6d1 --- /dev/null +++ b/tests/test_port_shard.hpp @@ -0,0 +1,170 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sysio::testing { + +inline constexpr const char* test_port_offset_env_var = "SYSIO_TEST_PORT_OFFSET"; +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; +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; +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; +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_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 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 { + ship, + state_history, + bios_http, + bios_p2p, + 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() { + const char* raw_offset = std::getenv(test_port_offset_env_var); + if(raw_offset == nullptr || raw_offset[0] == '\0') + return 0; + + 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. */ +inline uint16_t get_port(port_category category, uint32_t index = 0) { + const uint32_t offset = test_port_offset(); + uint32_t slot_start = 0; + uint32_t slot_count = 1; + uint32_t unsharded_base_port = 0; + + 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::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; + unsharded_base_port = compact_http_first_port; + break; + case port_category::alternate_service: + slot_start = compact_alternate_service_slot; + unsharded_base_port = 8976; + break; + case port_category::plugin_http_peer: + slot_start = compact_plugin_http_peer_slot; + unsharded_base_port = 9009; + break; + case port_category::plugin_http_local: + slot_start = compact_plugin_http_local_slot; + unsharded_base_port = 9011; + break; + 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; + 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 = compact_wallet_first_port; + break; + case port_category::transaction_only: + 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; + 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); +} + +/** 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(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..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} - ${CMAKE_SOURCE_DIR}/plugins/net_plugin/include) +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 84571a22ad..2e101d63e0 100644 --- a/tests/trx_generator/trx_generator_tests.cpp +++ b/tests/trx_generator/trx_generator_tests.cpp @@ -2,16 +2,78 @@ #include #include #include +#include #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 { @@ -366,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\"}}]"; @@ -488,7 +598,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(); 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 && \