From 864a9a9e2babb0fed988895ec9f97917db5132b3 Mon Sep 17 00:00:00 2001 From: Sergiu Osvat Date: Wed, 26 Nov 2025 15:34:50 +0200 Subject: [PATCH 1/9] add cleanup in actions --- .github/workflows/actions.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 38b7432e..0e3ef89f 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -66,7 +66,15 @@ jobs: WORKSPACE_ROOT: ${{ github.workspace }} MAX_TEST_CONCURRENCY: "4" run: | - cargo test --package rust-interact --test complete_flow_tests --all-features - cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features + cargo test --package rust-interact --test complete_flow_tests --all-features + cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features + - name: Cleanup chain simulator containers + if: always() + run: | + # Remove only the per-test chain simulator containers created by the wrapper + CONTAINERS=$(docker ps -a --filter "name=chain-sim-" --format "{{.ID}}") + if [ -n "$CONTAINERS" ]; then + docker rm -f $CONTAINERS + fi From 69bac117e77d60a439bc4b4316e9a19fbfe1e19b Mon Sep 17 00:00:00 2001 From: Sergiu Osvat Date: Thu, 27 Nov 2025 16:46:28 +0200 Subject: [PATCH 2/9] add improved logging --- .github/workflows/actions.yml | 7 ++++++ interactor/scripts/cargo-test-wrapper.py | 32 +++++++++++++----------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 0e3ef89f..98a27729 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -77,4 +77,11 @@ jobs: if [ -n "$CONTAINERS" ]; then docker rm -f $CONTAINERS fi + + - name: Interactor test summary + if: always() + run: | + if [ -f interactor_test_summary.md ]; then + cat interactor_test_summary.md >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/interactor/scripts/cargo-test-wrapper.py b/interactor/scripts/cargo-test-wrapper.py index 665bc7da..5b6471a1 100755 --- a/interactor/scripts/cargo-test-wrapper.py +++ b/interactor/scripts/cargo-test-wrapper.py @@ -9,7 +9,6 @@ import socket import subprocess import sys -import tempfile import time import urllib.error import urllib.request @@ -24,8 +23,8 @@ # Maximum number of test cases to run in parallel # Can be overridden via MAX_TEST_CONCURRENCY environment variable def get_max_concurrency() -> int: - """Get maximum concurrency from environment or default to 2.""" - return int(os.environ.get("MAX_TEST_CONCURRENCY", "2")) + """Get maximum concurrency from environment or default to 4.""" + return int(os.environ.get("MAX_TEST_CONCURRENCY", "4")) def remove_script_dir_from_path(script_dir: Path) -> str: @@ -210,7 +209,6 @@ def wait_for_simulator(port: int, container_name: str, max_attempts: int = 30) - return False - def filter_output(output: str) -> str: """Filter out duplicate and empty 'successes:' and 'failures:' sections. @@ -296,7 +294,6 @@ def run_parallel_tests(test_cases: List[str], args: List[str]) -> None: test_index = 0 try: - # Start initial batch of processes up to max_concurrency while test_index < len(test_cases) and len(processes) < max_concurrency: case_name = test_cases[test_index] case_args = list(args) @@ -323,7 +320,6 @@ def run_parallel_tests(test_cases: List[str], args: List[str]) -> None: processes[case_name] = process test_index += 1 - # Process completed tests and start new ones as slots become available while len(completed_processes) < len(test_cases): for case_name, process in list(processes.items()): if case_name in completed_processes: @@ -338,7 +334,6 @@ def run_parallel_tests(test_cases: List[str], args: List[str]) -> None: print_test_output(case_name, output, exit_code) - # Start next test if available if test_index < len(test_cases): next_case_name = test_cases[test_index] case_args = list(args) @@ -368,7 +363,6 @@ def run_parallel_tests(test_cases: List[str], args: List[str]) -> None: if len(completed_processes) < len(test_cases): time.sleep(0.1) - # Calculate summary statistics passed_tests = [] failed_tests = [] @@ -379,7 +373,6 @@ def run_parallel_tests(test_cases: List[str], args: List[str]) -> None: else: failed_tests.append(case_name) - # Print summary total_tests = len(test_cases) passed_count = len(passed_tests) failed_count = len(failed_tests) @@ -396,6 +389,21 @@ def run_parallel_tests(test_cases: List[str], args: List[str]) -> None: for test_name in failed_tests: print(f" - {test_name}", file=sys.stderr) + if os.environ.get("GITHUB_ACTIONS") == "true": + summary_path = os.environ.get("INTERACTOR_TEST_SUMMARY_PATH", "interactor_test_summary.md") + try: + with open(summary_path, "w", encoding="utf-8") as f: + f.write("## Interactor Test Summary\n\n") + f.write(f"- **Total tests**: {total_tests}\n") + f.write(f"- **Passed**: {passed_count}\n") + f.write(f"- **Failed**: {failed_count}\n\n") + if failed_tests: + f.write("### Failed tests\n") + for test_name in failed_tests: + f.write(f"- `{test_name}`\n") + except OSError: + pass + overall_exit_code = 0 if failed_count == 0 else 1 sys.exit(overall_exit_code) @@ -421,8 +429,6 @@ def start_simulator_container(port: int, container_name: str) -> bool: True if simulator started successfully, False otherwise. """ print(f"Starting chain simulator on port {port}...", file=sys.stderr) - # Add memory limit (2GB per container) to prevent OOM kills with 2 parallel containers - # With 16GB total RAM, 2 containers * 2GB = 4GB, leaving 12GB for system and other processes result = subprocess.run( ["docker", "run", "-d", "-p", f"{port}:8085", "--memory=2g", "--name", container_name, "multiversx/chainsimulator"], stdout=subprocess.DEVNULL, @@ -504,11 +510,9 @@ def main(): if test_cases: run_parallel_tests(test_cases, args) - # Check if CHAIN_SIMULATOR_PORT is already set (container started externally) port_str = os.environ.get("CHAIN_SIMULATOR_PORT") if port_str: port = int(port_str) - # Check if container already exists on this port result = subprocess.run( ["docker", "ps", "--filter", f"publish={port}", "--format", "{{.Names}}"], capture_output=True, @@ -518,13 +522,11 @@ def main(): if result.returncode == 0 and result.stdout.strip(): container_name = result.stdout.strip().split("\n")[0] print(f"Using existing chain simulator container '{container_name}' on port {port}", file=sys.stderr) - # Verify it's still running and ready if not wait_for_simulator(port, container_name, max_attempts=5): print(f"Warning: Container {container_name} on port {port} may not be ready", file=sys.stderr) exit_code = run_test(args, script_dir, port, test_file, test_name) sys.exit(exit_code) - # Otherwise, start a new container as before port = find_available_port() os.environ["CHAIN_SIMULATOR_PORT"] = str(port) From 91b7c0b65cf7a22c197e5bdaa4e67549084f45f0 Mon Sep 17 00:00:00 2001 From: Sergiu Osvat Date: Tue, 2 Dec 2025 15:25:15 +0200 Subject: [PATCH 3/9] try test summary --- interactor/scripts/cargo-test-wrapper.py | 72 ++++++++++++++++++------ 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/interactor/scripts/cargo-test-wrapper.py b/interactor/scripts/cargo-test-wrapper.py index 5b6471a1..7793c40c 100755 --- a/interactor/scripts/cargo-test-wrapper.py +++ b/interactor/scripts/cargo-test-wrapper.py @@ -254,6 +254,39 @@ def filter_output(output: str) -> str: return "\n".join(filtered_lines) +def write_test_summary(total_tests: int, passed_count: int, failed_count: int, failed_tests: List[str], workspace_root: str): + """Write test summary to file for GitHub Actions. + + Args: + total_tests: Total number of tests run. + passed_count: Number of tests that passed. + failed_count: Number of tests that failed. + failed_tests: List of failed test names. + workspace_root: Root directory of the workspace. + """ + if os.environ.get("GITHUB_ACTIONS") == "true": + summary_path = os.environ.get("INTERACTOR_TEST_SUMMARY_PATH") + if not summary_path: + summary_path = os.path.join(workspace_root, "interactor_test_summary.md") + else: + # If relative path provided, make it relative to workspace root + if not os.path.isabs(summary_path): + summary_path = os.path.join(workspace_root, summary_path) + + try: + with open(summary_path, "w", encoding="utf-8") as f: + f.write("## Interactor Test Summary\n\n") + f.write(f"- **Total tests**: {total_tests}\n") + f.write(f"- **Passed**: {passed_count}\n") + f.write(f"- **Failed**: {failed_count}\n\n") + if failed_tests: + f.write("### Failed tests\n") + for test_name in failed_tests: + f.write(f"- `{test_name}`\n") + except OSError as e: + print(f"Warning: Failed to write test summary: {e}", file=sys.stderr) + + def print_test_output(case_name: str, output: str, exit_code: int): """Print output for a completed test case with clear separators. @@ -276,12 +309,13 @@ def print_test_output(case_name: str, output: str, exit_code: int): print(f"{'='*80}\n", file=sys.stderr) -def run_parallel_tests(test_cases: List[str], args: List[str]) -> None: +def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: str) -> None: """Run multiple test cases in parallel with concurrency limit. Args: test_cases: List of test case names to run. args: Original cargo test arguments to reuse. + workspace_root: Root directory of the workspace. Exits with 0 if all tests pass, 1 if any fail, 130 on KeyboardInterrupt. """ @@ -389,20 +423,7 @@ def run_parallel_tests(test_cases: List[str], args: List[str]) -> None: for test_name in failed_tests: print(f" - {test_name}", file=sys.stderr) - if os.environ.get("GITHUB_ACTIONS") == "true": - summary_path = os.environ.get("INTERACTOR_TEST_SUMMARY_PATH", "interactor_test_summary.md") - try: - with open(summary_path, "w", encoding="utf-8") as f: - f.write("## Interactor Test Summary\n\n") - f.write(f"- **Total tests**: {total_tests}\n") - f.write(f"- **Passed**: {passed_count}\n") - f.write(f"- **Failed**: {failed_count}\n\n") - if failed_tests: - f.write("### Failed tests\n") - for test_name in failed_tests: - f.write(f"- `{test_name}`\n") - except OSError: - pass + write_test_summary(total_tests, passed_count, failed_count, failed_tests, workspace_root) overall_exit_code = 0 if failed_count == 0 else 1 sys.exit(overall_exit_code) @@ -508,10 +529,16 @@ def main(): test_cases = discover_test_cases(test_file, INTERACTOR_PACKAGE, workspace_root, filter_test_name) if test_cases: - run_parallel_tests(test_cases, args) + run_parallel_tests(test_cases, args, workspace_root) + else: + # No tests discovered, write empty summary + write_test_summary(0, 0, 0, [], workspace_root) + sys.exit(1) port_str = os.environ.get("CHAIN_SIMULATOR_PORT") if port_str: + # This is a child process running a specific test (spawned by parallel runner) + # Don't write summary here - parent process will write it port = int(port_str) result = subprocess.run( ["docker", "ps", "--filter", f"publish={port}", "--format", "{{.Names}}"], @@ -527,6 +554,7 @@ def main(): exit_code = run_test(args, script_dir, port, test_file, test_name) sys.exit(exit_code) + # This is a single test execution (not spawned by parallel runner) port = find_available_port() os.environ["CHAIN_SIMULATOR_PORT"] = str(port) @@ -534,16 +562,26 @@ def main(): container_name = f"chain-sim-{port}-{os.getpid()}-{int(time.time())}-{random_suffix}" exit_code = 0 + test_identifier = test_name if test_name else (test_file if test_file else "unknown") try: if not start_simulator_container(port, container_name): exit_code = 1 - exit_code = run_test(args, script_dir, port, test_file, test_name) + else: + exit_code = run_test(args, script_dir, port, test_file, test_name) + + # Write summary for single test execution + passed_count = 1 if exit_code == 0 else 0 + failed_count = 1 if exit_code != 0 else 0 + failed_tests = [test_identifier] if exit_code != 0 else [] + write_test_summary(1, passed_count, failed_count, failed_tests, workspace_root) except KeyboardInterrupt: exit_code = 130 + write_test_summary(1, 0, 1, [test_identifier], workspace_root) except Exception as e: print(f"Unexpected error: {e}", file=sys.stderr) exit_code = 1 + write_test_summary(1, 0, 1, [test_identifier], workspace_root) sys.exit(exit_code) From d48b6e525b9946198738e2e68228b56dc4ffbf97 Mon Sep 17 00:00:00 2001 From: Sergiu Osvat Date: Tue, 2 Dec 2025 15:59:12 +0200 Subject: [PATCH 4/9] try again interactor summary --- .github/workflows/actions.yml | 13 +++++--- interactor/scripts/cargo-test-wrapper.py | 40 +++++++++++++++++------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 98a27729..8cb0609d 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -65,9 +65,10 @@ jobs: PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} WORKSPACE_ROOT: ${{ github.workspace }} MAX_TEST_CONCURRENCY: "4" + GITHUB_ACTIONS: "true" run: | - cargo test --package rust-interact --test complete_flow_tests --all-features - cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features + cargo test --package rust-interact --test complete_flow_tests --all-features || true + cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features || true - name: Cleanup chain simulator containers if: always() @@ -81,7 +82,11 @@ jobs: - name: Interactor test summary if: always() run: | - if [ -f interactor_test_summary.md ]; then - cat interactor_test_summary.md >> "$GITHUB_STEP_SUMMARY" + if [ -f "${{ github.workspace }}/interactor_test_summary.md" ]; then + cat "${{ github.workspace }}/interactor_test_summary.md" >> "$GITHUB_STEP_SUMMARY" + else + echo "## Interactor Test Summary" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "⚠️ Summary file not found. Tests may have failed before summary could be written." >> "$GITHUB_STEP_SUMMARY" fi diff --git a/interactor/scripts/cargo-test-wrapper.py b/interactor/scripts/cargo-test-wrapper.py index 7793c40c..8481841c 100755 --- a/interactor/scripts/cargo-test-wrapper.py +++ b/interactor/scripts/cargo-test-wrapper.py @@ -254,7 +254,9 @@ def filter_output(output: str) -> str: return "\n".join(filtered_lines) -def write_test_summary(total_tests: int, passed_count: int, failed_count: int, failed_tests: List[str], workspace_root: str): +def write_test_summary( + total_tests: int, passed_count: int, failed_count: int, failed_tests: List[str], workspace_root: str, test_suite_name: Optional[str] = None +): """Write test summary to file for GitHub Actions. Args: @@ -263,6 +265,7 @@ def write_test_summary(total_tests: int, passed_count: int, failed_count: int, f failed_count: Number of tests that failed. failed_tests: List of failed test names. workspace_root: Root directory of the workspace. + test_suite_name: Optional name of the test suite (for multi-suite runs). """ if os.environ.get("GITHUB_ACTIONS") == "true": summary_path = os.environ.get("INTERACTOR_TEST_SUMMARY_PATH") @@ -274,15 +277,24 @@ def write_test_summary(total_tests: int, passed_count: int, failed_count: int, f summary_path = os.path.join(workspace_root, summary_path) try: - with open(summary_path, "w", encoding="utf-8") as f: - f.write("## Interactor Test Summary\n\n") + file_exists = os.path.exists(summary_path) + mode = "a" if file_exists else "w" + + with open(summary_path, mode, encoding="utf-8") as f: + if not file_exists: + f.write("## Interactor Test Summary\n\n") + + if test_suite_name: + f.write(f"### {test_suite_name}\n\n") + f.write(f"- **Total tests**: {total_tests}\n") f.write(f"- **Passed**: {passed_count}\n") f.write(f"- **Failed**: {failed_count}\n\n") if failed_tests: - f.write("### Failed tests\n") + f.write("#### Failed tests\n") for test_name in failed_tests: f.write(f"- `{test_name}`\n") + f.write("\n") except OSError as e: print(f"Warning: Failed to write test summary: {e}", file=sys.stderr) @@ -309,13 +321,14 @@ def print_test_output(case_name: str, output: str, exit_code: int): print(f"{'='*80}\n", file=sys.stderr) -def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: str) -> None: +def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: str, test_file: Optional[str] = None) -> None: """Run multiple test cases in parallel with concurrency limit. Args: test_cases: List of test case names to run. args: Original cargo test arguments to reuse. workspace_root: Root directory of the workspace. + test_file: Optional name of the test file (for summary). Exits with 0 if all tests pass, 1 if any fail, 130 on KeyboardInterrupt. """ @@ -423,7 +436,8 @@ def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: s for test_name in failed_tests: print(f" - {test_name}", file=sys.stderr) - write_test_summary(total_tests, passed_count, failed_count, failed_tests, workspace_root) + suite_name = test_file if test_file else "Test Suite" + write_test_summary(total_tests, passed_count, failed_count, failed_tests, workspace_root, suite_name) overall_exit_code = 0 if failed_count == 0 else 1 sys.exit(overall_exit_code) @@ -529,10 +543,11 @@ def main(): test_cases = discover_test_cases(test_file, INTERACTOR_PACKAGE, workspace_root, filter_test_name) if test_cases: - run_parallel_tests(test_cases, args, workspace_root) + run_parallel_tests(test_cases, args, workspace_root, test_file) else: # No tests discovered, write empty summary - write_test_summary(0, 0, 0, [], workspace_root) + suite_name = test_file if test_file else "Test Suite" + write_test_summary(0, 0, 0, [], workspace_root, suite_name) sys.exit(1) port_str = os.environ.get("CHAIN_SIMULATOR_PORT") @@ -573,15 +588,18 @@ def main(): passed_count = 1 if exit_code == 0 else 0 failed_count = 1 if exit_code != 0 else 0 failed_tests = [test_identifier] if exit_code != 0 else [] - write_test_summary(1, passed_count, failed_count, failed_tests, workspace_root) + suite_name = test_file if test_file else "Single Test" + write_test_summary(1, passed_count, failed_count, failed_tests, workspace_root, suite_name) except KeyboardInterrupt: exit_code = 130 - write_test_summary(1, 0, 1, [test_identifier], workspace_root) + suite_name = test_file if test_file else "Single Test" + write_test_summary(1, 0, 1, [test_identifier], workspace_root, suite_name) except Exception as e: print(f"Unexpected error: {e}", file=sys.stderr) exit_code = 1 - write_test_summary(1, 0, 1, [test_identifier], workspace_root) + suite_name = test_file if test_file else "Single Test" + write_test_summary(1, 0, 1, [test_identifier], workspace_root, suite_name) sys.exit(exit_code) From 0a1feaa89dab50885e7e1db2aef6c38deff52b21 Mon Sep 17 00:00:00 2001 From: Sergiu Osvat Date: Thu, 4 Dec 2025 16:54:27 +0200 Subject: [PATCH 5/9] improved cleanup and lock ports --- .github/workflows/actions.yml | 38 +++-- interactor/scripts/cargo-test-wrapper.py | 208 +++++++++++++++++++++-- 2 files changed, 222 insertions(+), 24 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 8cb0609d..58cf5e78 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -64,29 +64,41 @@ jobs: env: PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} WORKSPACE_ROOT: ${{ github.workspace }} - MAX_TEST_CONCURRENCY: "4" + MAX_TEST_CONCURRENCY: "2" GITHUB_ACTIONS: "true" run: | cargo test --package rust-interact --test complete_flow_tests --all-features || true - cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features || true - - - name: Cleanup chain simulator containers + + - name: Cleanup containers after first test suite if: always() run: | - # Remove only the per-test chain simulator containers created by the wrapper + # Clean up containers from first test suite before starting second CONTAINERS=$(docker ps -a --filter "name=chain-sim-" --format "{{.ID}}") if [ -n "$CONTAINERS" ]; then - docker rm -f $CONTAINERS + echo "Cleaning up containers: $CONTAINERS" + docker stop $CONTAINERS || true + docker rm -f $CONTAINERS || true fi + # Wait a moment for ports to be released + sleep 2 - - name: Interactor test summary + - name: Run second interactor test suite + env: + PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} + WORKSPACE_ROOT: ${{ github.workspace }} + MAX_TEST_CONCURRENCY: "2" + GITHUB_ACTIONS: "true" + run: | + cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features || true + + - name: Cleanup chain simulator containers if: always() run: | - if [ -f "${{ github.workspace }}/interactor_test_summary.md" ]; then - cat "${{ github.workspace }}/interactor_test_summary.md" >> "$GITHUB_STEP_SUMMARY" - else - echo "## Interactor Test Summary" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "⚠️ Summary file not found. Tests may have failed before summary could be written." >> "$GITHUB_STEP_SUMMARY" + # Remove only the per-test chain simulator containers created by the wrapper + CONTAINERS=$(docker ps -a --filter "name=chain-sim-" --format "{{.ID}}") + if [ -n "$CONTAINERS" ]; then + echo "Final cleanup of containers: $CONTAINERS" + docker stop $CONTAINERS || true + docker rm -f $CONTAINERS || true fi diff --git a/interactor/scripts/cargo-test-wrapper.py b/interactor/scripts/cargo-test-wrapper.py index 8481841c..64426289 100755 --- a/interactor/scripts/cargo-test-wrapper.py +++ b/interactor/scripts/cargo-test-wrapper.py @@ -4,11 +4,13 @@ Called by the cargo wrapper script when an interactor test is detected """ +import fcntl import os import random import socket import subprocess import sys +import tempfile import time import urllib.error import urllib.request @@ -137,43 +139,103 @@ def find_available_port() -> int: Generates a port based on PID, timestamp, and random component, then verifies it's not in use by checking socket binding and Docker. + Uses a lock file to prevent race conditions in parallel execution. Returns: An available port number between 1024 and 65535. Raises: - SystemExit: If no available port is found after 100 attempts. + SystemExit: If no available port is found after 200 attempts. """ base_port = 8085 - port = base_port + (os.getpid() % 1000) + (int(time.time()) % 1000) + random.randint(0, 99) + # Use a wider range to avoid collisions in parallel execution + port = base_port + (os.getpid() % 5000) + (int(time.time() * 1000) % 5000) + random.randint(0, 999) while port < 1024 or port > 65535: - port = base_port + (port % 1000) + random.randint(0, 99) + port = base_port + (port % 5000) + random.randint(0, 999) + + # Use a lock file to prevent race conditions + lock_file_path = os.path.join(tempfile.gettempdir(), f"port_lock_{port}.lock") + lock_file = None attempts = 0 - while attempts < 100: + while attempts < 200: # Increased attempts for better reliability + # Check if port is available via socket binding sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: sock.bind(("localhost", port)) sock.close() except OSError: - port += 1 + port = base_port + (port % 5000) + random.randint(0, 999) + if port < 1024 or port > 65535: + port = base_port + (port % 5000) + random.randint(0, 999) attempts += 1 continue - result = subprocess.run(["docker", "ps", "--filter", f"publish={port}", "--format", "{{.ID}}"], capture_output=True, text=True, check=False) + # Check Docker containers using both publish filter and name pattern + result = subprocess.run( + ["docker", "ps", "--filter", f"publish={port}", "--format", "{{.ID}}"], + capture_output=True, + text=True, + check=False, + timeout=5, + ) if result.returncode == 0 and result.stdout.strip(): - port += 1 + port = base_port + (port % 5000) + random.randint(0, 999) + if port < 1024 or port > 65535: + port = base_port + (port % 5000) + random.randint(0, 999) attempts += 1 continue - break + # Try to acquire lock on this port + try: + lock_file = open(lock_file_path, "w") + fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + # Double-check Docker after acquiring lock + result = subprocess.run( + ["docker", "ps", "--filter", f"publish={port}", "--format", "{{.ID}}"], + capture_output=True, + text=True, + check=False, + timeout=5, + ) + if result.returncode == 0 and result.stdout.strip(): + lock_file.close() + try: + os.remove(lock_file_path) + except OSError: + pass + port = base_port + (port % 5000) + random.randint(0, 999) + if port < 1024 or port > 65535: + port = base_port + (port % 5000) + random.randint(0, 999) + attempts += 1 + continue + # Port is available, lock acquired + break + except (IOError, OSError): + # Lock acquisition failed, port might be in use + if lock_file: + lock_file.close() + port = base_port + (port % 5000) + random.randint(0, 999) + if port < 1024 or port > 65535: + port = base_port + (port % 5000) + random.randint(0, 999) + attempts += 1 + continue - if port > 65535: - print("Failed to find available port after 100 attempts", file=sys.stderr) + if port > 65535 or attempts >= 200: + if lock_file: + lock_file.close() + try: + os.remove(lock_file_path) + except OSError: + pass + print("Failed to find available port after 200 attempts", file=sys.stderr) sys.exit(1) + # Lock will be released when process exits, but we keep it for now + # The lock file will be cleaned up on process exit return port @@ -338,6 +400,7 @@ def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: s processes = {} completed_processes = set() exit_codes = {} + container_names = {} # Track containers for cleanup test_index = 0 try: @@ -381,6 +444,22 @@ def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: s print_test_output(case_name, output, exit_code) + # Cleanup container for this test + # Extract port from output or environment if available + # Try to find container by test identifier + test_port = None + for line in output.split("\n"): + if "CHAIN_SIMULATOR_PORT" in line or "TEST_PORT" in line: + # Try to extract port + pass + if "chain-sim-" in line.lower(): + # Try to extract container name + pass + + # Cleanup all containers matching this test's pattern + # Since each test creates its own container, we'll clean up by port + # The child process should handle its own cleanup, but we'll do a final sweep + if test_index < len(test_cases): next_case_name = test_cases[test_index] case_args = list(args) @@ -410,6 +489,21 @@ def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: s if len(completed_processes) < len(test_cases): time.sleep(0.1) + # Final cleanup: only for local runs (GitHub Actions handles cleanup via workflow) + if not should_skip_cleanup(): + # Wait a moment for child processes to finish their cleanup + time.sleep(1) + result = subprocess.run( + ["docker", "ps", "-a", "--filter", "name=chain-sim-", "--format", "{{.Names}}"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0 and result.stdout.strip(): + for container_name in result.stdout.strip().split("\n"): + if container_name.strip(): + cleanup_container(container_name.strip()) + passed_tests = [] failed_tests = [] @@ -450,9 +544,87 @@ def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: s except subprocess.TimeoutExpired: process.kill() process.wait() + # Cleanup containers on interrupt (only for local runs) + if not should_skip_cleanup(): + result = subprocess.run( + ["docker", "ps", "-a", "--filter", "name=chain-sim-", "--format", "{{.Names}}"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0 and result.stdout.strip(): + for container_name in result.stdout.strip().split("\n"): + if container_name.strip(): + cleanup_container(container_name.strip()) sys.exit(130) +def should_skip_cleanup() -> bool: + """Check if cleanup should be skipped (e.g., in GitHub Actions where cleanup is handled by workflow). + + Returns: + True if cleanup should be skipped, False otherwise. + """ + return os.environ.get("GITHUB_ACTIONS") == "true" + + +def cleanup_container(container_name: str) -> None: + """Stop and remove a Docker container. + + Args: + container_name: Name of the container to clean up. + """ + # Skip cleanup in GitHub Actions - workflow handles it + if should_skip_cleanup(): + return + + # Check if container exists before attempting cleanup + check_result = subprocess.run( + ["docker", "ps", "-a", "--filter", f"name=^{container_name}$", "--format", "{{.Names}}"], + capture_output=True, + text=True, + check=False, + timeout=5, + ) + + if check_result.returncode != 0 or not check_result.stdout.strip(): + # Container doesn't exist, nothing to clean up + return + + try: + subprocess.run( + ["docker", "stop", container_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=10, + ) + subprocess.run( + ["docker", "rm", container_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=10, + ) + except subprocess.TimeoutExpired: + # Force kill if stop times out + subprocess.run( + ["docker", "kill", container_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + subprocess.run( + ["docker", "rm", "-f", container_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + except Exception: + # Ignore errors during cleanup + pass + + def start_simulator_container(port: int, container_name: str) -> bool: """Start the chain simulator Docker container and wait for it to be ready. @@ -477,6 +649,8 @@ def start_simulator_container(port: int, container_name: str) -> bool: if not wait_for_simulator(port, container_name): print("Chain simulator failed to start after 30 seconds", file=sys.stderr) + # Skip cleanup in Actions - workflow will handle it + cleanup_container(container_name) return False return True @@ -551,10 +725,12 @@ def main(): sys.exit(1) port_str = os.environ.get("CHAIN_SIMULATOR_PORT") + container_name = None if port_str: # This is a child process running a specific test (spawned by parallel runner) - # Don't write summary here - parent process will write it + # Each child creates its own container, so we need to track it for cleanup port = int(port_str) + # Check if container already exists (shouldn't happen in normal flow) result = subprocess.run( ["docker", "ps", "--filter", f"publish={port}", "--format", "{{.Names}}"], capture_output=True, @@ -567,6 +743,9 @@ def main(): if not wait_for_simulator(port, container_name, max_attempts=5): print(f"Warning: Container {container_name} on port {port} may not be ready", file=sys.stderr) exit_code = run_test(args, script_dir, port, test_file, test_name) + # Cleanup this container (each child manages its own) + if container_name: + cleanup_container(container_name) sys.exit(exit_code) # This is a single test execution (not spawned by parallel runner) @@ -583,6 +762,9 @@ def main(): exit_code = 1 else: exit_code = run_test(args, script_dir, port, test_file, test_name) + # Cleanup container after test completes + if container_name: + cleanup_container(container_name) # Write summary for single test execution passed_count = 1 if exit_code == 0 else 0 @@ -592,10 +774,14 @@ def main(): write_test_summary(1, passed_count, failed_count, failed_tests, workspace_root, suite_name) except KeyboardInterrupt: + if container_name: + cleanup_container(container_name) exit_code = 130 suite_name = test_file if test_file else "Single Test" write_test_summary(1, 0, 1, [test_identifier], workspace_root, suite_name) except Exception as e: + if container_name: + cleanup_container(container_name) print(f"Unexpected error: {e}", file=sys.stderr) exit_code = 1 suite_name = test_file if test_file else "Single Test" From 06c68974c43c15db5c3cea03128601afff167924 Mon Sep 17 00:00:00 2001 From: Sergiu Osvat Date: Thu, 4 Dec 2025 17:29:10 +0200 Subject: [PATCH 6/9] update max concurency and some cleanup --- .github/workflows/actions.yml | 4 +- interactor/scripts/cargo-test-wrapper.py | 63 ------------------------ 2 files changed, 2 insertions(+), 65 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 58cf5e78..774e2657 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -64,7 +64,7 @@ jobs: env: PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} WORKSPACE_ROOT: ${{ github.workspace }} - MAX_TEST_CONCURRENCY: "2" + MAX_TEST_CONCURRENCY: "4" GITHUB_ACTIONS: "true" run: | cargo test --package rust-interact --test complete_flow_tests --all-features || true @@ -86,7 +86,7 @@ jobs: env: PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} WORKSPACE_ROOT: ${{ github.workspace }} - MAX_TEST_CONCURRENCY: "2" + MAX_TEST_CONCURRENCY: "4" GITHUB_ACTIONS: "true" run: | cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features || true diff --git a/interactor/scripts/cargo-test-wrapper.py b/interactor/scripts/cargo-test-wrapper.py index 64426289..b5fd1e75 100755 --- a/interactor/scripts/cargo-test-wrapper.py +++ b/interactor/scripts/cargo-test-wrapper.py @@ -316,51 +316,6 @@ def filter_output(output: str) -> str: return "\n".join(filtered_lines) -def write_test_summary( - total_tests: int, passed_count: int, failed_count: int, failed_tests: List[str], workspace_root: str, test_suite_name: Optional[str] = None -): - """Write test summary to file for GitHub Actions. - - Args: - total_tests: Total number of tests run. - passed_count: Number of tests that passed. - failed_count: Number of tests that failed. - failed_tests: List of failed test names. - workspace_root: Root directory of the workspace. - test_suite_name: Optional name of the test suite (for multi-suite runs). - """ - if os.environ.get("GITHUB_ACTIONS") == "true": - summary_path = os.environ.get("INTERACTOR_TEST_SUMMARY_PATH") - if not summary_path: - summary_path = os.path.join(workspace_root, "interactor_test_summary.md") - else: - # If relative path provided, make it relative to workspace root - if not os.path.isabs(summary_path): - summary_path = os.path.join(workspace_root, summary_path) - - try: - file_exists = os.path.exists(summary_path) - mode = "a" if file_exists else "w" - - with open(summary_path, mode, encoding="utf-8") as f: - if not file_exists: - f.write("## Interactor Test Summary\n\n") - - if test_suite_name: - f.write(f"### {test_suite_name}\n\n") - - f.write(f"- **Total tests**: {total_tests}\n") - f.write(f"- **Passed**: {passed_count}\n") - f.write(f"- **Failed**: {failed_count}\n\n") - if failed_tests: - f.write("#### Failed tests\n") - for test_name in failed_tests: - f.write(f"- `{test_name}`\n") - f.write("\n") - except OSError as e: - print(f"Warning: Failed to write test summary: {e}", file=sys.stderr) - - def print_test_output(case_name: str, output: str, exit_code: int): """Print output for a completed test case with clear separators. @@ -530,9 +485,6 @@ def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: s for test_name in failed_tests: print(f" - {test_name}", file=sys.stderr) - suite_name = test_file if test_file else "Test Suite" - write_test_summary(total_tests, passed_count, failed_count, failed_tests, workspace_root, suite_name) - overall_exit_code = 0 if failed_count == 0 else 1 sys.exit(overall_exit_code) @@ -719,9 +671,6 @@ def main(): if test_cases: run_parallel_tests(test_cases, args, workspace_root, test_file) else: - # No tests discovered, write empty summary - suite_name = test_file if test_file else "Test Suite" - write_test_summary(0, 0, 0, [], workspace_root, suite_name) sys.exit(1) port_str = os.environ.get("CHAIN_SIMULATOR_PORT") @@ -756,7 +705,6 @@ def main(): container_name = f"chain-sim-{port}-{os.getpid()}-{int(time.time())}-{random_suffix}" exit_code = 0 - test_identifier = test_name if test_name else (test_file if test_file else "unknown") try: if not start_simulator_container(port, container_name): exit_code = 1 @@ -766,26 +714,15 @@ def main(): if container_name: cleanup_container(container_name) - # Write summary for single test execution - passed_count = 1 if exit_code == 0 else 0 - failed_count = 1 if exit_code != 0 else 0 - failed_tests = [test_identifier] if exit_code != 0 else [] - suite_name = test_file if test_file else "Single Test" - write_test_summary(1, passed_count, failed_count, failed_tests, workspace_root, suite_name) - except KeyboardInterrupt: if container_name: cleanup_container(container_name) exit_code = 130 - suite_name = test_file if test_file else "Single Test" - write_test_summary(1, 0, 1, [test_identifier], workspace_root, suite_name) except Exception as e: if container_name: cleanup_container(container_name) print(f"Unexpected error: {e}", file=sys.stderr) exit_code = 1 - suite_name = test_file if test_file else "Single Test" - write_test_summary(1, 0, 1, [test_identifier], workspace_root, suite_name) sys.exit(exit_code) From 238f523e748edef72698dbbc4310019bb3fc452a Mon Sep 17 00:00:00 2001 From: Sergiu Osvat Date: Fri, 5 Dec 2025 10:09:02 +0200 Subject: [PATCH 7/9] improved cleanup maybe --- .github/workflows/actions.yml | 2 +- interactor/scripts/cargo-test-wrapper.py | 235 +++++++++++++++-------- 2 files changed, 156 insertions(+), 81 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 774e2657..99888fa8 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -64,7 +64,7 @@ jobs: env: PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} WORKSPACE_ROOT: ${{ github.workspace }} - MAX_TEST_CONCURRENCY: "4" + MAX_TEST_CONCURRENCY: "2" GITHUB_ACTIONS: "true" run: | cargo test --package rust-interact --test complete_flow_tests --all-features || true diff --git a/interactor/scripts/cargo-test-wrapper.py b/interactor/scripts/cargo-test-wrapper.py index b5fd1e75..0be56aca 100755 --- a/interactor/scripts/cargo-test-wrapper.py +++ b/interactor/scripts/cargo-test-wrapper.py @@ -399,22 +399,6 @@ def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: s print_test_output(case_name, output, exit_code) - # Cleanup container for this test - # Extract port from output or environment if available - # Try to find container by test identifier - test_port = None - for line in output.split("\n"): - if "CHAIN_SIMULATOR_PORT" in line or "TEST_PORT" in line: - # Try to extract port - pass - if "chain-sim-" in line.lower(): - # Try to extract container name - pass - - # Cleanup all containers matching this test's pattern - # Since each test creates its own container, we'll clean up by port - # The child process should handle its own cleanup, but we'll do a final sweep - if test_index < len(test_cases): next_case_name = test_cases[test_index] case_args = list(args) @@ -444,20 +428,8 @@ def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: s if len(completed_processes) < len(test_cases): time.sleep(0.1) - # Final cleanup: only for local runs (GitHub Actions handles cleanup via workflow) - if not should_skip_cleanup(): - # Wait a moment for child processes to finish their cleanup - time.sleep(1) - result = subprocess.run( - ["docker", "ps", "-a", "--filter", "name=chain-sim-", "--format", "{{.Names}}"], - capture_output=True, - text=True, - check=False, - ) - if result.returncode == 0 and result.stdout.strip(): - for container_name in result.stdout.strip().split("\n"): - if container_name.strip(): - cleanup_container(container_name.strip()) + # Wait for all child processes to finish + time.sleep(1) passed_tests = [] failed_tests = [] @@ -485,6 +457,11 @@ def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: s for test_name in failed_tests: print(f" - {test_name}", file=sys.stderr) + # Final cleanup at the end of the process + # In GitHub Actions: only clean up containers from this process + # In local: comprehensive cleanup (all containers, networks, volumes) + cleanup_all_docker_resources() + overall_exit_code = 0 if failed_count == 0 else 1 sys.exit(overall_exit_code) @@ -496,40 +473,19 @@ def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: s except subprocess.TimeoutExpired: process.kill() process.wait() - # Cleanup containers on interrupt (only for local runs) - if not should_skip_cleanup(): - result = subprocess.run( - ["docker", "ps", "-a", "--filter", "name=chain-sim-", "--format", "{{.Names}}"], - capture_output=True, - text=True, - check=False, - ) - if result.returncode == 0 and result.stdout.strip(): - for container_name in result.stdout.strip().split("\n"): - if container_name.strip(): - cleanup_container(container_name.strip()) + # Final cleanup at the end of the process (even on interrupt) + # In GitHub Actions: only clean up containers from this process + # In local: comprehensive cleanup (all containers, networks, volumes) + cleanup_all_docker_resources() sys.exit(130) -def should_skip_cleanup() -> bool: - """Check if cleanup should be skipped (e.g., in GitHub Actions where cleanup is handled by workflow). - - Returns: - True if cleanup should be skipped, False otherwise. - """ - return os.environ.get("GITHUB_ACTIONS") == "true" - - def cleanup_container(container_name: str) -> None: """Stop and remove a Docker container. Args: container_name: Name of the container to clean up. """ - # Skip cleanup in GitHub Actions - workflow handles it - if should_skip_cleanup(): - return - # Check if container exists before attempting cleanup check_result = subprocess.run( ["docker", "ps", "-a", "--filter", f"name=^{container_name}$", "--format", "{{.Names}}"], @@ -577,6 +533,101 @@ def cleanup_container(container_name: str) -> None: pass +def is_github_actions() -> bool: + """Check if running in GitHub Actions environment. + + Returns: + True if running in GitHub Actions, False otherwise. + """ + return os.environ.get("GITHUB_ACTIONS") == "true" + + +def cleanup_all_docker_resources(container_name: Optional[str] = None) -> None: + """Clean up Docker resources created by tests. + + In GitHub Actions: Only cleans up the specified container (or containers matching current PID). + In local runs: Performs comprehensive cleanup of all chain-sim- containers, networks, and volumes. + + Args: + container_name: Optional specific container name to clean up. If None and in GitHub Actions, + only cleans up containers matching the current process PID. + """ + try: + if is_github_actions(): + # In GitHub Actions: only clean up containers from this process + if container_name: + # Clean up specific container + cleanup_container(container_name) + else: + # Clean up containers matching current PID pattern + current_pid = os.getpid() + result = subprocess.run( + ["docker", "ps", "-a", "--filter", f"name=chain-sim-", "--format", "{{.Names}}"], + capture_output=True, + text=True, + check=False, + timeout=10, + ) + + if result.returncode == 0 and result.stdout.strip(): + for name in result.stdout.strip().split("\n"): + if name.strip() and f"-{current_pid}-" in name: + cleanup_container(name.strip()) + else: + # Local runs: comprehensive cleanup + # Clean up all chain-sim- containers + result = subprocess.run( + ["docker", "ps", "-a", "--filter", "name=chain-sim-", "--format", "{{.ID}}"], + capture_output=True, + text=True, + check=False, + timeout=10, + ) + + if result.returncode == 0 and result.stdout.strip(): + container_ids = result.stdout.strip().split("\n") + for container_id in container_ids: + if container_id.strip(): + try: + subprocess.run( + ["docker", "stop", container_id.strip()], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=10, + ) + subprocess.run( + ["docker", "rm", "-f", container_id.strip()], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=10, + ) + except Exception: + pass + + # Clean up dangling networks + subprocess.run( + ["docker", "network", "prune", "-f"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=30, + ) + + # Clean up dangling volumes + subprocess.run( + ["docker", "volume", "prune", "-f"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=30, + ) + except Exception: + # Ignore errors during cleanup + pass + + def start_simulator_container(port: int, container_name: str) -> bool: """Start the chain simulator Docker container and wait for it to be ready. @@ -601,7 +652,6 @@ def start_simulator_container(port: int, container_name: str) -> bool: if not wait_for_simulator(port, container_name): print("Chain simulator failed to start after 30 seconds", file=sys.stderr) - # Skip cleanup in Actions - workflow will handle it cleanup_container(container_name) return False @@ -679,23 +729,47 @@ def main(): # This is a child process running a specific test (spawned by parallel runner) # Each child creates its own container, so we need to track it for cleanup port = int(port_str) - # Check if container already exists (shouldn't happen in normal flow) - result = subprocess.run( - ["docker", "ps", "--filter", f"publish={port}", "--format", "{{.Names}}"], - capture_output=True, - text=True, - check=False, - ) - if result.returncode == 0 and result.stdout.strip(): - container_name = result.stdout.strip().split("\n")[0] - print(f"Using existing chain simulator container '{container_name}' on port {port}", file=sys.stderr) - if not wait_for_simulator(port, container_name, max_attempts=5): - print(f"Warning: Container {container_name} on port {port} may not be ready", file=sys.stderr) - exit_code = run_test(args, script_dir, port, test_file, test_name) - # Cleanup this container (each child manages its own) + exit_code = 0 + + try: + # Check if container already exists (shouldn't happen in normal flow) + result = subprocess.run( + ["docker", "ps", "--filter", f"publish={port}", "--format", "{{.Names}}"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0 and result.stdout.strip(): + container_name = result.stdout.strip().split("\n")[0] + print(f"Using existing chain simulator container '{container_name}' on port {port}", file=sys.stderr) + if not wait_for_simulator(port, container_name, max_attempts=5): + print(f"Warning: Container {container_name} on port {port} may not be ready", file=sys.stderr) + else: + # Create a new container for this child process + random_suffix = random.randint(1000, 9999) + container_name = f"chain-sim-{port}-{os.getpid()}-{int(time.time())}-{random_suffix}" + if not start_simulator_container(port, container_name): + exit_code = 1 + container_name = None + + if exit_code == 0 and container_name: + exit_code = run_test(args, script_dir, port, test_file, test_name) + except KeyboardInterrupt: + exit_code = 130 + except Exception as e: + print(f"Unexpected error: {e}", file=sys.stderr) + exit_code = 1 + finally: + # Final cleanup at the end of the child process (always runs) if container_name: - cleanup_container(container_name) - sys.exit(exit_code) + if is_github_actions(): + cleanup_all_docker_resources(container_name) + else: + cleanup_container(container_name) + # Additional comprehensive cleanup for local runs + cleanup_all_docker_resources() + + sys.exit(exit_code) # This is a single test execution (not spawned by parallel runner) port = find_available_port() @@ -710,19 +784,20 @@ def main(): exit_code = 1 else: exit_code = run_test(args, script_dir, port, test_file, test_name) - # Cleanup container after test completes - if container_name: - cleanup_container(container_name) - except KeyboardInterrupt: - if container_name: - cleanup_container(container_name) exit_code = 130 except Exception as e: - if container_name: - cleanup_container(container_name) print(f"Unexpected error: {e}", file=sys.stderr) exit_code = 1 + finally: + # Final cleanup at the end of the process (always runs) + if container_name: + if is_github_actions(): + cleanup_all_docker_resources(container_name) + else: + cleanup_container(container_name) + # Additional comprehensive cleanup for local runs + cleanup_all_docker_resources() sys.exit(exit_code) From 62b9b87818d62cbdc5b8f166aefef15dd7381cbd Mon Sep 17 00:00:00 2001 From: Sergiu Osvat Date: Fri, 5 Dec 2025 14:17:40 +0200 Subject: [PATCH 8/9] update max concurency in second suite as well --- .github/workflows/actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 99888fa8..58cf5e78 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -86,7 +86,7 @@ jobs: env: PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} WORKSPACE_ROOT: ${{ github.workspace }} - MAX_TEST_CONCURRENCY: "4" + MAX_TEST_CONCURRENCY: "2" GITHUB_ACTIONS: "true" run: | cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features || true From 95e75dc722974d2502d098d45875f42f1f351904 Mon Sep 17 00:00:00 2001 From: Sergiu Osvat Date: Fri, 5 Dec 2025 15:25:22 +0200 Subject: [PATCH 9/9] fix possible timeout issue --- .github/workflows/actions.yml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 58cf5e78..47624ca1 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -31,6 +31,7 @@ jobs: interactor-tests: name: Interactor Tests runs-on: ubuntu-latest + timeout-minutes: 120 steps: - name: Checkout code uses: actions/checkout@v4 @@ -57,17 +58,18 @@ jobs: - name: Setup Docker run: | - sudo systemctl start docker || true - docker pull multiversx/chainsimulator || true + sudo systemctl start docker + docker pull multiversx/chainsimulator - - name: Run targeted interactor test via wrapper + - name: Run first interactor test suite + timeout-minutes: 60 env: PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} WORKSPACE_ROOT: ${{ github.workspace }} MAX_TEST_CONCURRENCY: "2" GITHUB_ACTIONS: "true" run: | - cargo test --package rust-interact --test complete_flow_tests --all-features || true + cargo test --package rust-interact --test complete_flow_tests --all-features - name: Cleanup containers after first test suite if: always() @@ -76,20 +78,21 @@ jobs: CONTAINERS=$(docker ps -a --filter "name=chain-sim-" --format "{{.ID}}") if [ -n "$CONTAINERS" ]; then echo "Cleaning up containers: $CONTAINERS" - docker stop $CONTAINERS || true - docker rm -f $CONTAINERS || true + docker stop $CONTAINERS + docker rm -f $CONTAINERS fi # Wait a moment for ports to be released sleep 2 - name: Run second interactor test suite + timeout-minutes: 60 env: PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} WORKSPACE_ROOT: ${{ github.workspace }} MAX_TEST_CONCURRENCY: "2" GITHUB_ACTIONS: "true" run: | - cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features || true + cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features - name: Cleanup chain simulator containers if: always() @@ -98,7 +101,7 @@ jobs: CONTAINERS=$(docker ps -a --filter "name=chain-sim-" --format "{{.ID}}") if [ -n "$CONTAINERS" ]; then echo "Final cleanup of containers: $CONTAINERS" - docker stop $CONTAINERS || true - docker rm -f $CONTAINERS || true + docker stop $CONTAINERS + docker rm -f $CONTAINERS fi