diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5d152fb..b1eb589a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: clients/cli proto tests + scripts - name: Check system resources run: | @@ -85,16 +86,16 @@ jobs: run: cargo test --release --tests # Integration test against production orchestrator - - name: Integration Test + - name: Integration Tests working-directory: clients/cli - timeout-minutes: 5 + timeout-minutes: 3 run: | - if [ -n "${{ secrets.SMOKE_TEST_NODE_IDS }}" ]; then - echo "Running integration test with secret node IDs" - SMOKE_TEST_NODE_IDS="${{ secrets.SMOKE_TEST_NODE_IDS }}" ../../tests/integration_test.sh ./target/release/nexus-network --max-tasks 1 + if [ -n "${{ secrets.INTEGRATION_TEST_NODE_IDS }}" ]; then + echo "Running integration tests with configured node IDs" + INTEGRATION_TEST_NODE_IDS="${{ secrets.INTEGRATION_TEST_NODE_IDS }}" bash "${{ github.workspace }}/scripts/run_integration_tests.sh" else - echo "Running integration test with fallback node IDs" - ../../tests/integration_test.sh ./target/release/nexus-network --max-tasks 1 + echo "Running integration tests with fallback node IDs" + bash "${{ github.workspace }}/scripts/run_integration_tests.sh" fi - name: Ensure checked in generated files are up to date diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 05eca6a3..2c85ae7f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,17 +3,26 @@ name: Release on: push: tags: - - "v*" # Trigger on tag push (e.g. "v1.0.0"). Adjust pattern as needed. - workflow_dispatch: # Allow manual triggering of the workflow. + - "v*" + workflow_dispatch: inputs: create_release: description: 'Create a GitHub release from the latest tag' required: true type: boolean default: false + docker_build: + description: 'Build Docker image (no push)' + required: true + type: boolean + default: false + docker_push: + description: 'Push Docker image to Docker Hub (only effective on refs/heads/main)' + required: true + type: boolean + default: false env: - # Set the default Rust toolchain to use for all jobs RUSTUP_TOOLCHAIN: nightly-2025-04-06 jobs: @@ -23,32 +32,78 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v5 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub + if: (github.event_name == 'push' || github.event.inputs.docker_push == 'true') && github.ref == 'refs/heads/main' uses: docker/login-action@v3 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - + - name: Build Docker image (no push) + if: github.event_name != 'push' && github.event.inputs.docker_build == 'true' && github.event.inputs.docker_push != 'true' + uses: docker/build-push-action@v6 + with: + context: clients/cli + file: clients/cli/Dockerfile + push: false + platforms: linux/amd64,linux/arm64 - name: Build and push Docker image + if: (github.event_name == 'push' || github.event.inputs.docker_push == 'true') && github.ref == 'refs/heads/main' uses: docker/build-push-action@v6 with: context: clients/cli - file: clients/cli/Dockerfile # Optional. Explicitly specify the Dockerfile path + file: clients/cli/Dockerfile push: true platforms: linux/amd64,linux/arm64 tags: | nexusxyz/nexus-cli:latest nexusxyz/nexus-cli:${{ github.sha }} - nexusxyz/nexus-cli:${{ github.ref_name }} + build: + name: Build ${{ matrix.id }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - id: linux-x86_64 + os: ubuntu-latest + target: x86_64-unknown-linux-gnu + build_cmd: cargo build --release --target x86_64-unknown-linux-gnu + rename_from: clients/cli/target/x86_64-unknown-linux-gnu/release/nexus-network + rename_to: clients/cli/target/x86_64-unknown-linux-gnu/release/nexus-network-linux-x86_64 + rustflags: "-C target-feature=+crt-static" + - id: linux-arm64 + os: ubuntu-latest + target: aarch64-unknown-linux-gnu + build_cmd: cargo build --release --target aarch64-unknown-linux-gnu + rename_from: clients/cli/target/aarch64-unknown-linux-gnu/release/nexus-network + rename_to: clients/cli/target/aarch64-unknown-linux-gnu/release/nexus-network-linux-arm64 + rustflags: "" + - id: macos-x86_64 + os: macos-13 + target: x86_64-apple-darwin + build_cmd: cargo +nightly-2025-04-06 build --release --target=x86_64-apple-darwin -Z build-std=std,panic_abort + rename_from: clients/cli/target/x86_64-apple-darwin/release/nexus-network + rename_to: clients/cli/target/x86_64-apple-darwin/release/nexus-network-macos-x86_64 + rustflags: "-C target-feature=+sse4.2,+avx,+avx2" + use_nightly: true + - id: macos-arm64 + os: macos-latest + target: aarch64-apple-darwin + build_cmd: cargo build --release --target=aarch64-apple-darwin + rename_from: clients/cli/target/aarch64-apple-darwin/release/nexus-network + rename_to: clients/cli/target/aarch64-apple-darwin/release/nexus-network-macos-arm64 + rustflags: "-C target-feature=+neon,+fp-armv8,+crc" + - id: windows-x86_64 + os: windows-latest + target: x86_64-pc-windows-msvc + build_cmd: cargo build --release --target=x86_64-pc-windows-msvc + rename_from: clients/cli/target/x86_64-pc-windows-msvc/release/nexus-network.exe + rename_to: clients/cli/target/x86_64-pc-windows-msvc/release/nexus-network-windows-x86_64.exe + rustflags: "" - build-linux-x86_64: - name: Build Linux (x86_64) - runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 @@ -58,144 +113,30 @@ jobs: workspaces: "clients/cli -> target" cache-all-crates: "true" cache-on-failure: "true" - key: "linux-x86_64" + key: ${{ matrix.id }} - name: Install & Use `mold` + if: startsWith(matrix.id, 'linux-') uses: rui314/setup-mold@v1 - # Set up the Rust toolchain for the specified target(s) - # When passing an explicit toolchain... you'll want to use "dtolnay/rust-toolchain@master" - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUSTUP_TOOLCHAIN }} - targets: x86_64-unknown-linux-gnu - components: rustfmt - - - name: Debug Rust environment - run: | - echo "Rust version: $(rustc --version --verbose)" - echo "Cargo version: $(cargo --version)" - echo "Rustup installed targets: $(rustup target list --installed | tr '\n' ',' | sed 's/,$//')" - echo "Rustup active toolchain: $(rustup show active-toolchain)" - echo "Host target: $(rustc -vV | grep host)" - - - name: Build Linux x86_64 binary - working-directory: clients/cli - run: cargo build --release --target x86_64-unknown-linux-gnu - env: - RUSTFLAGS: "-C target-feature=+crt-static" - - # Rename the binary to indicate the target OS - - name: Rename binary - working-directory: clients/cli/target/x86_64-unknown-linux-gnu/release/ - run: cp nexus-network nexus-network-linux-x86_64 - - # Upload the binary as an artifact - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: nexus-network-linux-x86_64 # Name of the artifact - path: clients/cli/target/x86_64-unknown-linux-gnu/release/nexus-network-linux-x86_64 # Path to file to upload - - - build-linux-arm64: - name: Build Linux (ARM64) - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: "clients/cli -> target" - cache-all-crates: "true" - cache-on-failure: "true" - key: "linux-arm64" - - - name: Install & Use `mold` - uses: rui314/setup-mold@v1 - - # Set up the Rust toolchain for the specified target(s) - # When passing an explicit toolchain... you'll want to use "dtolnay/rust-toolchain@master" - - name: Install Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUSTUP_TOOLCHAIN }} - targets: aarch64-unknown-linux-gnu - components: rustfmt - - # Install the required targets for cross-compilation - - name: Ensure targets are installed (fallback) - run: | - rustup target add aarch64-unknown-linux-gnu - rustup component add rust-src --toolchain ${{ env.RUSTUP_TOOLCHAIN }} + targets: ${{ matrix.target }} + components: rustfmt, rust-src - - name: Install ARM64 Linux linker + - name: Install cross linker for aarch64 + if: matrix.id == 'linux-arm64' run: | sudo apt-get update - sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross - - - name: Debug Rust environment - run: | - echo "Rust version: $(rustc --version --verbose)" - echo "Cargo version: $(cargo --version)" - echo "Rustup installed targets: $(rustup target list --installed | tr '\n' ',' | sed 's/,$//')" - echo "Rustup active toolchain: $(rustup show active-toolchain)" - echo "Host target: $(rustc -vV | grep host)" - - - name: Build Linux ARM64 binary (build-std) - working-directory: clients/cli - run: | - export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc - cargo build -Zbuild-std=std,panic_abort --release --target aarch64-unknown-linux-gnu - env: - RUSTFLAGS: "-C target-feature=+crt-static" - - # Rename the binary to indicate the target OS - - name: Rename binary - working-directory: clients/cli/target/aarch64-unknown-linux-gnu/release/ - run: cp nexus-network nexus-network-linux-arm64 - - # Upload the binary as an artifact - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: nexus-network-linux-arm64 # Name of the artifact - path: clients/cli/target/aarch64-unknown-linux-gnu/release/nexus-network-linux-arm64 # Path to file to upload - - build-macos-x86_64: - name: Build macOS (x86_64) - runs-on: macos-latest - # needs: build-linux # Ensure Linux job (and release creation) runs first - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: "clients/cli -> target" - cache-all-crates: "true" - cache-on-failure: "true" - key: "macos-x86_64" - - - name: Install & Use `mold` - uses: rui314/setup-mold@v1 + sudo apt-get install -y gcc-aarch64-linux-gnu - # Set up the Rust toolchain for the specified target(s) - # When passing an explicit toolchain... you'll want to use "dtolnay/rust-toolchain@master" - - name: Install Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUSTUP_TOOLCHAIN }} - targets: x86_64-apple-darwin - components: rustfmt, rust-src - - # Install the required targets for cross-compilation - - name: Ensure targets are installed (fallback) + - name: Configure cross-linker env + if: matrix.id == 'linux-arm64' run: | - rustup target add x86_64-apple-darwin - rustup component add rust-src --toolchain ${{ env.RUSTUP_TOOLCHAIN }} + echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV + echo "PKG_CONFIG_ALLOW_CROSS=1" >> $GITHUB_ENV - name: Debug Rust environment run: | @@ -205,153 +146,60 @@ jobs: echo "Rustup active toolchain: $(rustup show active-toolchain)" echo "Host target: $(rustc -vV | grep host)" - # Build the release binary for the specified target. Explicitly using the nightly toolchain. - - name: Build CLI binary + - name: Build CLI working-directory: clients/cli - run: cargo +nightly-2025-04-06 build --release --target=x86_64-apple-darwin -Z build-std=std,panic_abort + run: ${{ matrix.build_cmd }} env: - RUSTUP_TOOLCHAIN: ${{ env.RUSTUP_TOOLCHAIN }} - RUSTC_BOOTSTRAP: 1 - RUSTFLAGS: "-C target-feature=+sse4.2,+avx,+avx2" + RUSTFLAGS: ${{ matrix.rustflags }} + RUSTC_BOOTSTRAP: ${{ matrix.use_nightly && '1' || '' }} - # Rename the binary to indicate the target OS - name: Rename binary - working-directory: clients/cli/target/x86_64-apple-darwin/release/ - run: cp nexus-network nexus-network-macos-x86_64 - - - # Upload the binary as an artifact - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: nexus-network-macos-x86_64 # Name of the artifact - path: clients/cli/target/x86_64-apple-darwin/release/nexus-network-macos-x86_64 # Path to file to upload - - - build-macos-arm64: - name: Build macOS (ARM64) - runs-on: macos-latest - # needs: build-linux # Ensure Linux job (and release creation) runs first - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: "clients/cli -> target" - cache-all-crates: "true" - cache-on-failure: "true" - key: "macos-arm64" - - - name: Install & Use `mold` - uses: rui314/setup-mold@v1 + run: cp "${{ matrix.rename_from }}" "${{ matrix.rename_to }}" - # Set up the Rust toolchain for the specified target(s) - # When passing an explicit toolchain... you'll want to use "dtolnay/rust-toolchain@master" - - name: Install Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUSTUP_TOOLCHAIN }} - targets: aarch64-apple-darwin - components: rustfmt, rust-src - - - name: Debug Rust environment - run: | - echo "Rust version: $(rustc --version --verbose)" - echo "Cargo version: $(cargo --version)" - echo "Rustup installed targets: $(rustup target list --installed | tr '\n' ',' | sed 's/,$//')" - echo "Rustup active toolchain: $(rustup show active-toolchain)" - echo "Host target: $(rustc -vV | grep host)" - - # Build the release binary for the specified target - - name: Build CLI binary + - name: Integration Tests + if: matrix.id != 'linux-arm64' + shell: bash working-directory: clients/cli - run: cargo build --release --target=aarch64-apple-darwin - env: - RUSTFLAGS: "-C target-feature=+neon,+fp-armv8,+crc" - - # Rename the binary to indicate the target OS and platform - - name: Rename binary - working-directory: clients/cli/target/aarch64-apple-darwin/release/ - run: cp nexus-network nexus-network-macos-arm64 - - # Upload the binary as an artifact - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: nexus-network-macos-arm64 # Name of the artifact - path: clients/cli/target/aarch64-apple-darwin/release/nexus-network-macos-arm64 # Path to file to upload - - - build-windows-x86_64: - name: Build Windows (x86_64) - runs-on: windows-latest - - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: "clients/cli -> target" - cache-all-crates: "true" - cache-on-failure: "true" - key: "windows-x86_64" - - # Set up the Rust toolchain for the specified target(s) - # When passing an explicit toolchain... you'll want to use "dtolnay/rust-toolchain@master" - - name: Install Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUSTUP_TOOLCHAIN }} - targets: x86_64-pc-windows-msvc - components: rustfmt, rust-src - - - name: Debug Rust environment + timeout-minutes: 5 run: | - echo "Rust version: $(rustc --version --verbose)" - echo "Cargo version: $(cargo --version)" - echo "Rustup installed targets: $(rustup target list --installed | tr '\n' ',' | sed 's/,$//')" - echo "Rustup active toolchain: $(rustup show active-toolchain)" - echo "Host target: $(rustc -vV | grep host)" + ROOT_UNIX=$(echo "$GITHUB_WORKSPACE" | tr '\\' '/') + echo "Host triple: $(rustc -vV | awk '/host/ {print $2}')" + echo "Built binaries:" + find target -maxdepth 3 -type f -name 'nexus-network*' -print || true + if [ "${{ matrix.id }}" = "windows-x86_64" ]; then + echo "System memory info (Windows)" + wmic computersystem get TotalPhysicalMemory | findstr /r /v "^$" || true + wmic OS get FreePhysicalMemory | findstr /r /v "^$" || true + export RUST_BACKTRACE=1 + fi + if [ -n "${{ secrets.INTEGRATION_TEST_NODE_IDS }}" ]; then + echo "Running integration tests with configured node IDs" + INTEGRATION_TEST_NODE_IDS="${{ secrets.INTEGRATION_TEST_NODE_IDS }}" bash "$ROOT_UNIX/scripts/run_integration_tests.sh" + else + echo "Running integration tests with fallback node IDs" + bash "$ROOT_UNIX/scripts/run_integration_tests.sh" + fi - # Build the release binary for the specified target - - name: Build CLI binary - working-directory: clients/cli - run: cargo build --release --target=x86_64-pc-windows-msvc - env: - RUSTFLAGS: "-C target-feature=+sse4.2,+avx,+avx2" - - # Rename the binary to indicate the target OS and platform - - name: Rename binary - working-directory: clients/cli/target/x86_64-pc-windows-msvc/release/ - run: cp nexus-network.exe nexus-network-windows-x86_64.exe - - # Upload the binary as an artifact - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: nexus-network-windows-x86_64.exe # Name of the artifact - path: clients/cli/target/x86_64-pc-windows-msvc/release/nexus-network-windows-x86_64.exe # Path to file to upload - + name: nexus-network-${{ matrix.id }} + path: ${{ matrix.rename_to }} release: name: Create Release - needs: [ build-linux-x86_64, build-linux-arm64, build-macos-x86_64, build-macos-arm64, build-windows-x86_64 ] + needs: [ build ] if: github.event.inputs.create_release == 'true' || github.event_name == 'push' runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - name: Download all artifacts uses: actions/download-artifact@v4 with: path: artifacts merge-multiple: true - - name: List downloaded artifacts (debug) run: ls -lh artifacts/ - - name: Generate individual .sha256 files run: | cd artifacts @@ -361,22 +209,12 @@ jobs: fi done ls -lh - - name: Create Release id: create_release uses: softprops/action-gh-release@v2 with: files: | - artifacts/nexus-network-macos-arm64 - artifacts/nexus-network-macos-arm64.sha256 - artifacts/nexus-network-macos-x86_64 - artifacts/nexus-network-macos-x86_64.sha256 - artifacts/nexus-network-linux-arm64 - artifacts/nexus-network-linux-arm64.sha256 - artifacts/nexus-network-linux-x86_64 - artifacts/nexus-network-linux-x86_64.sha256 - artifacts/nexus-network-windows-x86_64.exe - artifacts/nexus-network-windows-x86_64.exe.sha256 + artifacts/* draft: false prerelease: false generate_release_notes: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0744d164..6ef5c3d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,7 +64,7 @@ Before submitting changes, please run the following checks locally: cargo fmt --check # Run clippy lints -cargo clippy -- -D warnings +a cargo clippy -- -D warnings # Check for unused dependencies cargo udeps @@ -84,6 +84,22 @@ By default, the build process skips proto compilation to make it easier for cont cargo build --features build_proto ``` +### Integration Tests (production orchestrator) + +We provide a unified script that runs the integration tests for the Rust CLI. No arguments required; the scripts use `cargo run` and sane defaults. + +```bash +# From repository root +INTEGRATION_TEST_NODE_IDS="id1,id2" ./scripts/run_integration_tests.sh +``` + +- Positive test: succeeds if the CLI exits 0 (assumes a proof was submitted with `--max-tasks 1`). +- Retry: runs at most twice; second attempt only if the first was rate-limited (429 detected). +- Negative test: runs with an invalid node id and passes only if the CLI exits non-zero. +- Exit codes: the wrapper returns non-zero on failures; 429 indicates persistent rate limiting. + +Set `INTEGRATION_TEST_NODE_IDS` (comma-separated) to run against specific production nodes. + ### Code of Conduct The Nexus network project adheres to the [Rust Code of Conduct][rust-coc]. This code of conduct describes the _minimum_ behavior diff --git a/README.md b/README.md index 48701fab..f224ebe5 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ A high-performance command-line interface for contributing proofs to the Nexus network. +> Want to help improve the CLI? We welcome contributions! See the [Contributor Guide](./CONTRIBUTING.md). To run end-to-end integration tests locally, use `./scripts/run_integration_tests.sh` (details in the guide). +
Nexus Network visualization showing a distributed network of interconnected nodes with a 'Launch Network' button in the center @@ -159,6 +161,9 @@ Interested in contributing to the Nexus Network CLI? Check out our - How to report issues and submit pull requests - Our code of conduct and community guidelines - Tips for working with the codebase +- How to run the end-to-end integration tests locally: `./scripts/run_integration_tests.sh` + +We welcome PRs—feel free to open a draft early to get feedback. For most users, we recommend using the precompiled binaries as described above. The contributor guide is intended for those who want to modify or improve the CLI diff --git a/clients/cli/src/session/headless_mode.rs b/clients/cli/src/session/headless_mode.rs index 36c4617c..3d9d98c0 100644 --- a/clients/cli/src/session/headless_mode.rs +++ b/clients/cli/src/session/headless_mode.rs @@ -7,6 +7,7 @@ use super::{ use crate::print_cmd_info; use crate::version::checker::check_for_new_version; use std::error::Error; +use std::io::{self, Write}; /// Runs the application in headless mode /// @@ -49,12 +50,49 @@ pub async fn run_headless_mode(mut session: SessionData) -> Result<(), Box { - println!("{}", event); + let line = format!("{}", event); + println!("{}", line); + // On Windows CI, mirror all output to stderr to avoid buffering issues with piped stdout + if cfg!(windows) && std::env::var("CI").is_ok() { + eprintln!("{}", line); + let _ = io::stderr().flush(); + } } _ = shutdown_receiver.recv() => { + // Only attempt a brief drain in CI on Windows to help flush final lines + if cfg!(windows) && std::env::var("CI").is_ok() { + for _ in 0..100 { + match session.event_receiver.try_recv() { + Ok(event) => { + let line = format!("{}", event); + println!("{}", line); + eprintln!("{}", line); + let _ = io::stderr().flush(); + } + Err(_) => { + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } + } + } + } break; } _ = max_tasks_shutdown_receiver.recv() => { + if cfg!(windows) && std::env::var("CI").is_ok() { + for _ in 0..100 { + match session.event_receiver.try_recv() { + Ok(event) => { + let line = format!("{}", event); + println!("{}", line); + eprintln!("{}", line); + let _ = io::stderr().flush(); + } + Err(_) => { + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } + } + } + } break; } } diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh new file mode 100755 index 00000000..409238c9 --- /dev/null +++ b/scripts/run_integration_tests.sh @@ -0,0 +1,159 @@ +#!/bin/bash + +# Master integration test runner (repo root). Runs all CLI integration tests. +# Usage: ./scripts/run_integration_tests.sh + +set -e +set -o pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CLI_DIR="$ROOT_DIR/clients/cli" + +YELLOW='\033[1;33m' +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +info() { echo -e "${YELLOW}[INFO]${NC} $1"; } +pass() { echo -e "${GREEN}[PASS]${NC} $1"; } +fail() { echo -e "${RED}[FAIL]${NC} $1"; } + +cd "$CLI_DIR" + +# Require a prebuilt release binary (from the build job) to avoid rebuilds during CI. +resolve_prebuilt_binary() { + HOST_TRIPLE=$(rustc -vV | awk '/host/ {print $2}') + BIN_PATH="target/${HOST_TRIPLE}/release/nexus-network" + if [ -f "${BIN_PATH}.exe" ]; then + echo "${BIN_PATH}.exe" + return 0 + fi + if [ -f "$BIN_PATH" ]; then + echo "$BIN_PATH" + return 0 + fi + # Also check default target dir (without explicit target), just in case + if [ -f "target/release/nexus-network" ]; then + echo "target/release/nexus-network" + return 0 + fi + if [ -f "target/release/nexus-network.exe" ]; then + echo "target/release/nexus-network.exe" + return 0 + fi + # No prebuilt binary found + echo "" +} + +run_cli() { + BIN=$(resolve_prebuilt_binary) + if [ -z "$BIN" ] || [ ! -x "$BIN" ]; then + fail "Prebuilt binary not found. Please run the Build step first." + exit 1 + fi + RUST_LOG=warn "$BIN" start "$@" +} + +run_positive_test() { + info "Starting positive integration test (prebuilt binary required)..." + ulimit -c 0 || true + + # Node IDs + NODE_IDS_ENV="${INTEGRATION_TEST_NODE_IDS}" + if [ -n "$NODE_IDS_ENV" ]; then + IFS=',' read -ra NODE_IDS <<<"$NODE_IDS_ENV" + else + NODE_IDS=("6166715" "23716208" "23519580" "23718361") + fi + if command -v shuf >/dev/null 2>&1; then + NODE_IDS=($(printf '%s\n' "${NODE_IDS[@]}" | shuf)) + fi + + ATTEMPT_COUNT=0 + for node_id in "${NODE_IDS[@]}"; do + if [ $ATTEMPT_COUNT -ge 2 ]; then + break + fi + ATTEMPT_COUNT=$((ATTEMPT_COUNT+1)) + + if [ -n "$GITHUB_ACTIONS" ] || [ -n "$CI" ]; then + TEMP_RAW_OUTPUT="$PWD/integration_positive.log" + rm -f "$TEMP_RAW_OUTPUT" 2>/dev/null || true + else + TEMP_RAW_OUTPUT=$(mktemp) + trap "rm -f $TEMP_RAW_OUTPUT" EXIT + fi + info "Running CLI (attempt $ATTEMPT_COUNT) with node $node_id..." + + set +e + run_cli --headless --max-tasks 1 --node-id $node_id 2>&1 | tee "$TEMP_RAW_OUTPUT" + CLI_EXIT_CODE=$? + set -e + + if [ "$CLI_EXIT_CODE" -eq 0 ]; then + pass "Positive test passed" + return 0 + fi + + if grep -q "Rate limit exceeded" "$TEMP_RAW_OUTPUT" 2>/dev/null || grep -q '"httpCode":429' "$TEMP_RAW_OUTPUT" 2>/dev/null; then + if [ $ATTEMPT_COUNT -lt 2 ]; then + info "Rate limited (attempt $ATTEMPT_COUNT). Retrying with next node id..." + rm -f "$TEMP_RAW_OUTPUT" + continue + else + fail "Positive test failed - Rate limited across attempts" + return 429 + fi + else + info "CLI output (last 10 lines):"; tail -10 "$TEMP_RAW_OUTPUT" || true + fail "Positive test failed - Non-zero exit (not rate limited)" + return 1 + fi + done + + fail "Positive test failed - No proof submission detected" + return 1 +} + +run_negative_test() { + info "Starting negative integration test (invalid node id)..." + ulimit -c 0 || true + INVALID_NODE_ID="0" + if [ -n "$GITHUB_ACTIONS" ] || [ -n "$CI" ]; then + TMP_OUT="$PWD/integration_negative.log" + rm -f "$TMP_OUT" 2>/dev/null || true + else + TMP_OUT=$(mktemp) + trap "rm -f $TMP_OUT" EXIT + fi + + set +e + run_cli --headless --max-tasks 1 --node-id "$INVALID_NODE_ID" 2>&1 | tee "$TMP_OUT" + EXIT_CODE=$? + set -e + + if [ "$EXIT_CODE" -ne 0 ]; then + pass "Negative test passed (non-zero exit: $EXIT_CODE)" + return 0 + else + fail "Negative test failed (CLI exited 0 with invalid node id)" + info "Last 20 lines of output:"; tail -20 "$TMP_OUT" || true + return 1 + fi +} + +# Run tests sequentially in foreground +run_positive_test +POSITIVE_CODE=$? +if [ $POSITIVE_CODE -ne 0 ]; then + exit $POSITIVE_CODE +fi + +run_negative_test +NEGATIVE_CODE=$? +if [ $NEGATIVE_CODE -ne 0 ]; then + exit $NEGATIVE_CODE +fi + +info "All integration tests passed" +exit 0 \ No newline at end of file diff --git a/tests/integration_test.sh b/tests/integration_test.sh deleted file mode 100755 index a9a4c234..00000000 --- a/tests/integration_test.sh +++ /dev/null @@ -1,220 +0,0 @@ -#!/bin/bash - -# Nexus CLI Integration Test -# This script runs the CLI in headless mode and verifies it can submit proofs to production -# Usage: ./integration_test.sh [binary_path] [node_id] [--max-tasks] -# Example: ./integration_test.sh ./target/release/nexus-network 6166715 --max-tasks 1 - -set -e - -# Disable core dumps globally -ulimit -c 0 - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -echo -e "${YELLOW}Starting Nexus CLI Integration Test...${NC}" - -# Function to print colored output -print_status() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -print_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -print_info() { - echo -e "${YELLOW}[INFO]${NC} $1" -} - -# Configuration -NODE_ID="${2:-6166715}" # Use second argument or default (fallback) -MAX_TIMEOUT_SECONDS=180 # 3 minutes max timeout -SUCCESS_PATTERN="Step 4 of 4: Proof submitted successfully" -JUST_ONCE=false - -# Check for --max-tasks parameter (could be in position 2 or 3) -if [[ "$2" == "--max-tasks" ]] || [[ "$3" == "--max-tasks" ]]; then - JUST_ONCE=true - print_info "Running with max-tasks=1 - will exit after first proof or rate limiting" -fi - -# Parse node IDs from environment variable (GitHub secret) or use fallback -if [ -n "$SMOKE_TEST_NODE_IDS" ]; then - # Split comma-separated string into array - IFS=',' read -ra NODE_IDS <<<"$SMOKE_TEST_NODE_IDS" -elif [ "$2" != "--max-tasks" ] && [ -n "$2" ]; then - # If a specific node ID was provided as argument, use only that one - NODE_IDS=("$2") -else - # Fallback node IDs if secret not available - NODE_IDS=( - "6166715" - "23716208" - "23519580" - "23718361" - ) -fi - -# Get binary path from command line argument or use default -BINARY_PATH="${1:-./target/release/nexus-network}" - -# Check if the CLI binary exists -if [ ! -f "$BINARY_PATH" ]; then - print_error "CLI binary not found at: $BINARY_PATH" - print_info "Usage: $0 [binary_path] [node_id]" - print_info "Example: $0 ./target/release/nexus-network " - exit 1 -fi - -print_info "Using binary: $BINARY_PATH" -print_info "Monitoring for: $SUCCESS_PATTERN" - -# Shuffle the node IDs array to load balance -NODE_IDS=($(printf '%s\n' "${NODE_IDS[@]}" | sort -R)) - -# Try each node ID until one works -for node_id in "${NODE_IDS[@]}"; do - - # Use temporary files to capture output - TEMP_OUTPUT=$(mktemp) - TEMP_RAW_OUTPUT=$(mktemp) - trap "rm -f $TEMP_OUTPUT $TEMP_RAW_OUTPUT" EXIT - - # Start the CLI process and capture output with timeout - print_info "Starting CLI process..." - - # Start the CLI process in background (release mode should have clean output) - ( - ulimit -c 0 - RUST_LOG=warn "$BINARY_PATH" start --headless --max-tasks 1 --node-id $node_id 2>&1 | tee "$TEMP_RAW_OUTPUT" - ) & - CLI_PID=$! - - # Wait for either completion or timeout (60 seconds) - TIMEOUT=60 - for i in $(seq 1 $TIMEOUT); do - if ! kill -0 "$CLI_PID" 2>/dev/null; then - # Process has finished - wait "$CLI_PID" - CLI_EXIT_CODE=$? - break - fi - - # Check for success pattern every 5 seconds and show progress - if [ $((i % 10)) -eq 0 ]; then - print_info "CLI still running... ($i/$TIMEOUT seconds)" - if [ -f "$TEMP_RAW_OUTPUT" ]; then - # Show the last few lines to see progress - LAST_LINES=$(tail -3 "$TEMP_RAW_OUTPUT" 2>/dev/null) - if [ -n "$LAST_LINES" ]; then - print_info "Recent activity:" - echo "$LAST_LINES" | while IFS= read -r line; do - echo " $line" - done - fi - fi - fi - - if [ $((i % 5)) -eq 0 ] && [ -f "$TEMP_RAW_OUTPUT" ]; then - if grep -q "$SUCCESS_PATTERN" "$TEMP_RAW_OUTPUT" 2>/dev/null; then - print_status "Success pattern detected early, waiting for clean exit..." - # Give it 30 more seconds to exit cleanly - for j in $(seq 1 30); do - if ! kill -0 "$CLI_PID" 2>/dev/null; then - print_info "CLI exited cleanly after success" - wait "$CLI_PID" 2>/dev/null - CLI_EXIT_CODE=$? - break 2 - fi - sleep 1 - done - # If still running after 30 seconds, terminate it - if kill -0 "$CLI_PID" 2>/dev/null; then - print_info "CLI still running after 30s, terminating..." - kill -TERM "$CLI_PID" 2>/dev/null - sleep 2 - if kill -0 "$CLI_PID" 2>/dev/null; then - kill -KILL "$CLI_PID" 2>/dev/null - fi - wait "$CLI_PID" 2>/dev/null - CLI_EXIT_CODE=$? - fi - break - fi - fi - - sleep 1 - done - - # If we reached timeout, kill the process - if kill -0 "$CLI_PID" 2>/dev/null; then - print_info "CLI process timed out after $TIMEOUT seconds, terminating..." - kill -TERM "$CLI_PID" 2>/dev/null - sleep 2 - if kill -0 "$CLI_PID" 2>/dev/null; then - kill -KILL "$CLI_PID" 2>/dev/null - fi - wait "$CLI_PID" 2>/dev/null - CLI_EXIT_CODE=$? - fi - - if [ "$CLI_EXIT_CODE" -eq 0 ]; then - # Process completed successfully - print_info "CLI process completed successfully" - - if grep -q "$SUCCESS_PATTERN" "$TEMP_RAW_OUTPUT" 2>/dev/null; then - print_status "Success pattern detected: $SUCCESS_PATTERN" - SUCCESS_FOUND=true - else - print_info "No success pattern found in output" - fi - else - # Process failed or was terminated - if [ "$CLI_EXIT_CODE" -eq 143 ]; then - # Process was terminated by SIGTERM (timeout or signal) - print_info "Process terminated by signal" - elif [ "$CLI_EXIT_CODE" -eq 124 ]; then - # Process timed out - print_info "Process timed out" - else - print_info "Process exited with code: $CLI_EXIT_CODE" - fi - - if grep -q "Rate limited" "$TEMP_RAW_OUTPUT" 2>/dev/null; then - RATE_LIMITED=true - fi - fi - - # Show last few lines of CLI output for debugging - print_info "CLI output (last 10 lines):" - tail -10 "$TEMP_RAW_OUTPUT" 2>/dev/null | while IFS= read -r line; do - echo " $line" - done - - # Check if we found the success pattern - if [ "$SUCCESS_FOUND" = true ]; then - print_status "Integration test PASSED - CLI successfully submitted proof" - exit 0 - elif [ "$JUST_ONCE" = true ] && [ "$EXIT_EARLY" = true ]; then - # In --once mode, continue to next node ID if rate limited - continue - else - if [ "$RATE_LIMITED" = true ]; then - print_info "Rate limited" - fi - fi - - # Clean up temp files - rm -f "$TEMP_OUTPUT" "$TEMP_RAW_OUTPUT" -done - -# If we get here, none of the node IDs worked -print_error "Integration test FAILED - No proof submission detected within $MAX_TIMEOUT_SECONDS seconds" -print_info "Checked for success patterns:" -echo " - $SUCCESS_PATTERN" -exit 1