From 3ad3be70d6dce461f87eff26dc87c61d121ea7fa Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 16:04:11 +0000 Subject: [PATCH 01/14] public reproduce ci script --- bin/reproduce_ci.sh | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 bin/reproduce_ci.sh diff --git a/bin/reproduce_ci.sh b/bin/reproduce_ci.sh new file mode 100755 index 0000000..8604622 --- /dev/null +++ b/bin/reproduce_ci.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -euo pipefail + +# Public wrapper: fetches the real CI reproduction scripts from er_build_tools_internal (private) +# and runs them. This script is the entry point for remote execution via: +# bash <(curl -Ls https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/main/bin/reproduce_ci.sh) \ +# --gh-token ghp_xxx --repo https://github.com/extend-robotics/er_interface + +gh_token="" +scripts_branch="main" +for i in $(seq 1 $#); do + arg="${!i}" + if [ "${arg}" = "--gh-token" ] || [ "${arg}" = "-t" ]; then + next=$((i + 1)) + if [ "${next}" -gt "$#" ]; then + echo "Error: ${arg} requires a value" + exit 1 + fi + gh_token="${!next}" + elif [ "${arg}" = "--scripts-branch" ]; then + next=$((i + 1)) + if [ "${next}" -gt "$#" ]; then + echo "Error: ${arg} requires a value" + exit 1 + fi + scripts_branch="${!next}" + fi +done + +if [ -z "${gh_token}" ]; then + echo "Error: --gh-token is required to fetch scripts from er_build_tools_internal" + echo "Usage: bash <(curl -Ls ...) --gh-token --repo [options]" + exit 1 +fi + +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "${TEMP_DIR}"' EXIT + +RAW_URL="https://raw.githubusercontent.com/Extend-Robotics/er_build_tools_internal/refs/heads/${scripts_branch}" + +echo "Fetching scripts from er_build_tools_internal (branch: ${scripts_branch})..." +curl -sfH "Authorization: token ${gh_token}" "${RAW_URL}/bin/reproduce_ci.sh" -o "${TEMP_DIR}/reproduce_ci.sh" +curl -sfH "Authorization: token ${gh_token}" "${RAW_URL}/bin/ci_workspace_setup.sh" -o "${TEMP_DIR}/ci_workspace_setup.sh" + +chmod +x "${TEMP_DIR}/reproduce_ci.sh" +"${TEMP_DIR}/reproduce_ci.sh" "$@" From 666a35460ebaf475772094c0b9117581a36e29ad Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 16:09:56 +0000 Subject: [PATCH 02/14] no mktemp --- bin/reproduce_ci.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/reproduce_ci.sh b/bin/reproduce_ci.sh index 8604622..d102d64 100755 --- a/bin/reproduce_ci.sh +++ b/bin/reproduce_ci.sh @@ -33,14 +33,14 @@ if [ -z "${gh_token}" ]; then exit 1 fi -TEMP_DIR=$(mktemp -d) -trap 'rm -rf "${TEMP_DIR}"' EXIT +SCRIPT_DIR="/tmp/er_reproduce_ci" +mkdir -p "${SCRIPT_DIR}" RAW_URL="https://raw.githubusercontent.com/Extend-Robotics/er_build_tools_internal/refs/heads/${scripts_branch}" echo "Fetching scripts from er_build_tools_internal (branch: ${scripts_branch})..." -curl -sfH "Authorization: token ${gh_token}" "${RAW_URL}/bin/reproduce_ci.sh" -o "${TEMP_DIR}/reproduce_ci.sh" -curl -sfH "Authorization: token ${gh_token}" "${RAW_URL}/bin/ci_workspace_setup.sh" -o "${TEMP_DIR}/ci_workspace_setup.sh" +curl -sfH "Authorization: token ${gh_token}" "${RAW_URL}/bin/reproduce_ci.sh" -o "${SCRIPT_DIR}/reproduce_ci.sh" +curl -sfH "Authorization: token ${gh_token}" "${RAW_URL}/bin/ci_workspace_setup.sh" -o "${SCRIPT_DIR}/ci_workspace_setup.sh" -chmod +x "${TEMP_DIR}/reproduce_ci.sh" -"${TEMP_DIR}/reproduce_ci.sh" "$@" +chmod +x "${SCRIPT_DIR}/reproduce_ci.sh" +"${SCRIPT_DIR}/reproduce_ci.sh" "$@" From 51072cb81674576ec715eb3b9782c10c9712f48b Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 16:44:38 +0000 Subject: [PATCH 03/14] verbose --- bin/reproduce_ci.sh | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/bin/reproduce_ci.sh b/bin/reproduce_ci.sh index d102d64..aa5c93f 100755 --- a/bin/reproduce_ci.sh +++ b/bin/reproduce_ci.sh @@ -34,13 +34,41 @@ if [ -z "${gh_token}" ]; then fi SCRIPT_DIR="/tmp/er_reproduce_ci" +echo "Creating script directory: ${SCRIPT_DIR}" mkdir -p "${SCRIPT_DIR}" RAW_URL="https://raw.githubusercontent.com/Extend-Robotics/er_build_tools_internal/refs/heads/${scripts_branch}" +fetch_script() { + local script_name="$1" + local source_url="${RAW_URL}/bin/${script_name}" + local destination="${SCRIPT_DIR}/${script_name}" + + echo "Fetching ${script_name}:" + echo " From: ${source_url}" + echo " To: ${destination}" + + local http_code + http_code=$(curl -fL -w "%{http_code}" -H "Authorization: token ${gh_token}" "${source_url}" -o "${destination}" 2>&1) || { + echo "Error: Failed to fetch ${script_name}" + echo " HTTP response: ${http_code}" + echo " Check that the branch '${scripts_branch}' exists in er_build_tools_internal" + echo " Check that your --gh-token has access to Extend-Robotics/er_build_tools_internal" + exit 1 + } + echo " HTTP ${http_code} - OK" + + if [ ! -s "${destination}" ]; then + echo "Error: Downloaded file is empty: ${destination}" + exit 1 + fi +} + echo "Fetching scripts from er_build_tools_internal (branch: ${scripts_branch})..." -curl -sfH "Authorization: token ${gh_token}" "${RAW_URL}/bin/reproduce_ci.sh" -o "${SCRIPT_DIR}/reproduce_ci.sh" -curl -sfH "Authorization: token ${gh_token}" "${RAW_URL}/bin/ci_workspace_setup.sh" -o "${SCRIPT_DIR}/ci_workspace_setup.sh" +fetch_script "reproduce_ci.sh" +fetch_script "ci_workspace_setup.sh" chmod +x "${SCRIPT_DIR}/reproduce_ci.sh" +echo "" +echo "Running ${SCRIPT_DIR}/reproduce_ci.sh..." "${SCRIPT_DIR}/reproduce_ci.sh" "$@" From 443665703817aa25f3d4cc6e0041f8877db85b94 Mon Sep 17 00:00:00 2001 From: tomqext Date: Fri, 13 Feb 2026 17:13:21 +0000 Subject: [PATCH 04/14] skip checks --- bin/reproduce_ci.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/reproduce_ci.sh b/bin/reproduce_ci.sh index aa5c93f..596b280 100755 --- a/bin/reproduce_ci.sh +++ b/bin/reproduce_ci.sh @@ -1,4 +1,6 @@ #!/bin/bash + +# SKIP_CHECK set -euo pipefail # Public wrapper: fetches the real CI reproduction scripts from er_build_tools_internal (private) From 776787fbc54adfc487ed430d5e6815a88d80d91b Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 17:23:23 +0000 Subject: [PATCH 05/14] README.md --- README.md | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/README.md b/README.md index 3041107..fc96d31 100644 --- a/README.md +++ b/README.md @@ -1 +1,95 @@ # er_build_tools + +Public build tools and utilities for Extend Robotics repositories. + +## reproduce_ci.sh — Reproduce CI Locally + +When CI fails, debugging requires pushing commits and waiting for results. This script reproduces the exact CI environment locally in a persistent Docker container, so you can debug interactively. + +It creates a Docker container using the same image as CI, clones your repo and its dependencies, builds everything, and optionally runs tests — mirroring the steps in `setup_and_build_ros_ws.yml`. + +### Quick Start + +```bash +bash <(curl -Ls https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/main/bin/reproduce_ci.sh) \ + --gh-token "$GH_TOKEN" \ + --repo https://github.com/Extend-Robotics/er_interface \ + --only-needed-deps +``` + +### Requirements + +- Docker installed and running +- A GitHub token (`--gh-token`) with access to Extend-Robotics private repos + +### Options + +| Flag | Short | Default | Description | +|------|-------|---------|-------------| +| `--gh-token` | `-t` | *required* | GitHub token with access to private repos | +| `--repo` | `-r` | *required* | Repository URL to test | +| `--branch` | `-b` | `main` | Branch or commit SHA to test | +| `--only-needed-deps` | | off | Only build deps needed by the repo under test (faster) | +| `--skip-tests` | | off | Skip running colcon tests | +| `--image` | `-i` | `rostooling/setup-ros-docker:ubuntu-focal-ros-noetic-desktop-latest` | Docker image | +| `--container-name` | `-n` | `er_ci_reproduced_testing_env` | Docker container name | +| `--deps-file` | `-d` | `deps.repos` | Path to deps file in the repo | +| `--graphical` | `-g` | `true` | Enable X11/NVIDIA forwarding | +| `--additional-command` | `-c` | | Extra command to run after build/test | +| `--scripts-branch` | | `main` | Branch of `er_build_tools_internal` to fetch scripts from | + +### Examples + +Test a specific branch with all deps: + +```bash +bash <(curl -Ls https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/main/bin/reproduce_ci.sh) \ + --gh-token "$GH_TOKEN" \ + --repo https://github.com/Extend-Robotics/er_interface \ + --branch my-feature-branch +``` + +Build only, skip tests, no graphical forwarding: + +```bash +bash <(curl -Ls https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/main/bin/reproduce_ci.sh) \ + --gh-token "$GH_TOKEN" \ + --repo https://github.com/Extend-Robotics/er_interface \ + --only-needed-deps \ + --skip-tests \ + --graphical false +``` + +Run xacro lint after build (like er_interface CI does): + +```bash +bash <(curl -Ls https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/main/bin/reproduce_ci.sh) \ + --gh-token "$GH_TOKEN" \ + --repo https://github.com/Extend-Robotics/er_interface \ + --only-needed-deps \ + --additional-command "python3 ros_ws/src/er_interface/er_interface/src/er_interface/xacro_lint.py" +``` + +### After the Script Completes + +The container stays running. You can enter it to debug interactively: + +```bash +docker exec -it er_ci_reproduced_testing_env bash +``` + +The workspace is at `/ros_ws` inside the container. + +To clean up: + +```bash +docker rm -f er_ci_reproduced_testing_env +``` + +### Troubleshooting + +**Container already exists** — Remove it first: `docker rm -f er_ci_reproduced_testing_env` + +**404 when fetching scripts** — Check that your `--gh-token` has access to `er_build_tools_internal`, and that the `--scripts-branch` exists. + +**`DISPLAY` error with graphical forwarding** — Either set `DISPLAY` (e.g. via X11 forwarding) or pass `--graphical false`. From d258aa1fb97b8c4d50be11a2e1a7d1f08d6c5ec8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 17:23:50 +0000 Subject: [PATCH 06/14] more verbose --- bin/reproduce_ci.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bin/reproduce_ci.sh b/bin/reproduce_ci.sh index aa5c93f..8076e69 100755 --- a/bin/reproduce_ci.sh +++ b/bin/reproduce_ci.sh @@ -17,7 +17,7 @@ for i in $(seq 1 $#); do exit 1 fi gh_token="${!next}" - elif [ "${arg}" = "--scripts-branch" ]; then + elif [ "${arg}" = "--scripts-branch" ] || [ "${arg}" = "--scripts_branch" ]; then next=$((i + 1)) if [ "${next}" -gt "$#" ]; then echo "Error: ${arg} requires a value" @@ -49,14 +49,15 @@ fetch_script() { echo " To: ${destination}" local http_code - http_code=$(curl -fL -w "%{http_code}" -H "Authorization: token ${gh_token}" "${source_url}" -o "${destination}" 2>&1) || { + http_code=$(curl -fL -w "%{http_code}" -H "Authorization: token ${gh_token}" "${source_url}" -o "${destination}" 2>/dev/null) || { + echo " FAILED (HTTP ${http_code})" + echo "" echo "Error: Failed to fetch ${script_name}" - echo " HTTP response: ${http_code}" echo " Check that the branch '${scripts_branch}' exists in er_build_tools_internal" echo " Check that your --gh-token has access to Extend-Robotics/er_build_tools_internal" exit 1 } - echo " HTTP ${http_code} - OK" + echo " OK (HTTP ${http_code})" if [ ! -s "${destination}" ]; then echo "Error: Downloaded file is empty: ${destination}" From b240c7d8e992f340c2156ca459a8314bbfca7e0c Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 17:29:30 +0000 Subject: [PATCH 07/14] more examples --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index fc96d31..5f56e6a 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,16 @@ bash <(curl -Ls https://raw.githubusercontent.com/Extend-Robotics/er_build_tools --graphical false ``` +Test with feature branches of both `er_build_tools` and `er_build_tools_internal` (useful when developing the CI scripts themselves): + +```bash +bash <(curl -Ls https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/my-feature-branch-in-er-build-tools/bin/reproduce_ci.sh) \ + --gh-token "$GH_TOKEN" \ + --repo https://github.com/Extend-Robotics/er_interface \ + --scripts-branch my-feature-branch-in-er-build-tools-internal \ + --only-needed-deps +``` + Run xacro lint after build (like er_interface CI does): ```bash From c327e1d9f853f7e9e1d2d7b4932be43114af754e Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 18:02:22 +0000 Subject: [PATCH 08/14] silent failures bad --- bin/reproduce_ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/reproduce_ci.sh b/bin/reproduce_ci.sh index a72cc6a..d71f921 100755 --- a/bin/reproduce_ci.sh +++ b/bin/reproduce_ci.sh @@ -51,7 +51,7 @@ fetch_script() { echo " To: ${destination}" local http_code - http_code=$(curl -fL -w "%{http_code}" -H "Authorization: token ${gh_token}" "${source_url}" -o "${destination}" 2>/dev/null) || { + http_code=$(curl -fSL -w "%{http_code}" -H "Authorization: token ${gh_token}" "${source_url}" -o "${destination}") || { echo " FAILED (HTTP ${http_code})" echo "" echo "Error: Failed to fetch ${script_name}" From be46ca4c94d0acc8619e2bc740390fb9a2c7015b Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 23:51:54 +0000 Subject: [PATCH 09/14] Add CI reproduction bash helper functions Add reproduce_ci, repull_and_rerun_ci_tests, and remove_ci_container functions to .helper_bash_functions. reproduce_ci defaults --gh-token from ER_SETUP_TOKEN env var. Wrapper now also fetches the retest script. --- .helper_bash_functions | 39 +++++++++++++++++++++++++++++++++++++++ bin/reproduce_ci.sh | 1 + 2 files changed, 40 insertions(+) diff --git a/.helper_bash_functions b/.helper_bash_functions index d8a1a76..495a97e 100644 --- a/.helper_bash_functions +++ b/.helper_bash_functions @@ -135,6 +135,45 @@ er_pylint_sorted_here() { er_pylint_here | sort -V | grep -v "\*\*\*\*\*\*\*\*"; er_pylint_single_file() { check_pylintrc; check_apt_package "pylint"; pylint --rcfile /tmp/pylintrc --max-line-length ${LINTER_MAX_LINE_LENGTH} $1; } er_pylint_single_file_sorted() { er_pylint_single_file $1 | sort -V | grep -v "\*\*\*\*\*\*\*\*"; return ${PIPESTATUS[0]}; } er_ruff_here() { ruff check; } +DEFAULT_CI_CONTAINER_NAME="er_ci_reproduced_testing_env" +reproduce_ci() { + local token="${ER_SETUP_TOKEN:-}" + local args=("$@") + local has_token="false" + for arg in "${args[@]}"; do + if [ "${arg}" = "--gh-token" ] || [ "${arg}" = "-t" ]; then + has_token="true" + break + fi + done + if [ "${has_token}" = "false" ]; then + if [ -z "${token}" ]; then + echo -e "${Red}Error: --gh-token not provided and ER_SETUP_TOKEN is not set${Color_Off}" + return 1 + fi + args=("--gh-token" "${token}" "${args[@]}") + fi + bash <(curl -Ls https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/main/bin/reproduce_ci.sh) "${args[@]}" +} +repull_and_rerun_ci_tests() { + local container_name="${1:-${DEFAULT_CI_CONTAINER_NAME}}" + if ! docker ps --filter "name=^${container_name}$" --format '{{.Names}}' | grep -q .; then + echo -e "${Red}Error: Container '${container_name}' is not running${Color_Off}" + return 1 + fi + docker exec "${container_name}" bash /tmp/ci_repull_and_retest.sh +} +remove_ci_container() { + local container_name="${1:-${DEFAULT_CI_CONTAINER_NAME}}" + if ! docker ps -a --filter "name=^${container_name}$" --format '{{.Names}}' | grep -q .; then + echo -e "${Yellow}Container '${container_name}' does not exist${Color_Off}" + return 0 + fi + echo "Stopping and removing container '${container_name}'..." + docker rm -f "${container_name}" + echo -e "${Green}Container '${container_name}' removed${Color_Off}" +} + er_python_linters_here() { local ret=0 echo er_pylint_sorted_here || ret=$? diff --git a/bin/reproduce_ci.sh b/bin/reproduce_ci.sh index d71f921..1037807 100755 --- a/bin/reproduce_ci.sh +++ b/bin/reproduce_ci.sh @@ -70,6 +70,7 @@ fetch_script() { echo "Fetching scripts from er_build_tools_internal (branch: ${scripts_branch})..." fetch_script "reproduce_ci.sh" fetch_script "ci_workspace_setup.sh" +fetch_script "ci_repull_and_retest.sh" chmod +x "${SCRIPT_DIR}/reproduce_ci.sh" echo "" From 471ec5f70985ae2f79487b663ae9fb7a1b477b76 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 23:55:06 +0000 Subject: [PATCH 10/14] tidy --- .helper_bash_functions | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.helper_bash_functions b/.helper_bash_functions index 495a97e..a7f15b6 100644 --- a/.helper_bash_functions +++ b/.helper_bash_functions @@ -135,6 +135,9 @@ er_pylint_sorted_here() { er_pylint_here | sort -V | grep -v "\*\*\*\*\*\*\*\*"; er_pylint_single_file() { check_pylintrc; check_apt_package "pylint"; pylint --rcfile /tmp/pylintrc --max-line-length ${LINTER_MAX_LINE_LENGTH} $1; } er_pylint_single_file_sorted() { er_pylint_single_file $1 | sort -V | grep -v "\*\*\*\*\*\*\*\*"; return ${PIPESTATUS[0]}; } er_ruff_here() { ruff check; } + + +# reproduce ci locally DEFAULT_CI_CONTAINER_NAME="er_ci_reproduced_testing_env" reproduce_ci() { local token="${ER_SETUP_TOKEN:-}" @@ -174,6 +177,7 @@ remove_ci_container() { echo -e "${Green}Container '${container_name}' removed${Color_Off}" } + er_python_linters_here() { local ret=0 echo er_pylint_sorted_here || ret=$? From 3cd8201b79457d700c2fefe599244ce4f8b9dc66 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Feb 2026 23:59:53 +0000 Subject: [PATCH 11/14] more verbose --- .helper_bash_functions | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.helper_bash_functions b/.helper_bash_functions index a7f15b6..f7e6c80 100644 --- a/.helper_bash_functions +++ b/.helper_bash_functions @@ -156,7 +156,12 @@ reproduce_ci() { fi args=("--gh-token" "${token}" "${args[@]}") fi - bash <(curl -Ls https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/main/bin/reproduce_ci.sh) "${args[@]}" + local wrapper_script + wrapper_script=$(curl -fSL https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/main/bin/reproduce_ci.sh) || { + echo -e "${Red}Error: Failed to fetch reproduce_ci.sh wrapper from er_build_tools main branch${Color_Off}" + return 1 + } + bash <(echo "${wrapper_script}") "${args[@]}" } repull_and_rerun_ci_tests() { local container_name="${1:-${DEFAULT_CI_CONTAINER_NAME}}" From 01e611a69f41d461ff2f29cbc7ee623e7ede140c Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 14 Feb 2026 00:09:11 +0000 Subject: [PATCH 12/14] wrapper, better error --- .helper_bash_functions | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.helper_bash_functions b/.helper_bash_functions index f7e6c80..99f0f3a 100644 --- a/.helper_bash_functions +++ b/.helper_bash_functions @@ -143,6 +143,7 @@ reproduce_ci() { local token="${ER_SETUP_TOKEN:-}" local args=("$@") local has_token="false" + local CI_BRANCH="ERD-1633_reproduce_ci_locally" for arg in "${args[@]}"; do if [ "${arg}" = "--gh-token" ] || [ "${arg}" = "-t" ]; then has_token="true" @@ -183,6 +184,7 @@ remove_ci_container() { } + er_python_linters_here() { local ret=0 echo er_pylint_sorted_here || ret=$? From aae9c956179735dc33fe71ca0a2663092a6c8814 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 17 Feb 2026 23:30:03 +0000 Subject: [PATCH 13/14] bug fix --- .helper_bash_functions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.helper_bash_functions b/.helper_bash_functions index 99f0f3a..a7f7235 100644 --- a/.helper_bash_functions +++ b/.helper_bash_functions @@ -158,7 +158,7 @@ reproduce_ci() { args=("--gh-token" "${token}" "${args[@]}") fi local wrapper_script - wrapper_script=$(curl -fSL https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/main/bin/reproduce_ci.sh) || { + wrapper_script=$(curl -fSL https://raw.githubusercontent.com/Extend-Robotics/er_build_tools/refs/heads/${CI_BRANCH}/bin/reproduce_ci.sh) || { echo -e "${Red}Error: Failed to fetch reproduce_ci.sh wrapper from er_build_tools main branch${Color_Off}" return 1 } From 4e956286bc30acf272a056dd49523dad760b0a19 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Feb 2026 21:09:09 +0000 Subject: [PATCH 14/14] Reduce test output verbosity for colcon test results Show minimal output when all tests pass. On failure, parse JUnit XMLs from test_results/ (skipping CTest XMLs that contain entire roslaunch logs) and display only assertion errors and tracebacks. --- .helper_bash_functions | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.helper_bash_functions b/.helper_bash_functions index a7f7235..6f6bf8f 100644 --- a/.helper_bash_functions +++ b/.helper_bash_functions @@ -26,13 +26,33 @@ colcon_build() { START_DIR=$(pwd) && \ } colcon_build_this() { colcon_build "$(find_cmake_project_names_from_dir .)"; } rosdep_install() { rosdep install --from-paths src --ignore-src -r -y ; } +_show_test_failures() { + python3 - "$@" <<'PYEOF' +import sys, xml.etree.ElementTree as ET +from pathlib import Path +for d in sys.argv[1:]: + for p in sorted(Path(d).rglob("*.xml")): + for tc in ET.parse(p).iter("testcase"): + for f in list(tc.iter("failure")) + list(tc.iter("error")): + tag = "FAIL" if f.tag == "failure" else "ERROR" + print(f"\n {tag}: {tc.get('classname', '')}.{tc.get('name', '')}") + if f.text: + lines = f.text.strip().splitlines() + for l in lines[:20]: + print(f" {l}") + if len(lines) > 20: + print(f" ... ({len(lines) - 20} more lines)") +PYEOF +} colcon_test_these_packages() { THIS_DIR=$(pwd) cd ${CATKIN_WS_PATH} colcon_build_no_deps $1 source install/setup.bash && \ colcon test --packages-select $1 for pkg in $1; do - colcon test-result --verbose --test-result-base "build/$pkg" + if ! colcon test-result --test-result-base "build/$pkg/test_results"; then + _show_test_failures "build/$pkg/test_results" + fi done cd $THIS_DIR