diff --git a/README.md b/README.md index fd55d28..6c23e49 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Qubership Testing Platform Common Scripts -ATP3 submodule for ATP3-runners that contains base bash scripts. +ATP3 submodule for ATP3-runners that contains base Bash scripts. -# Modular Test Execution Scripts +## Modular Test Execution Scripts This directory contains modular scripts for test execution in containerized environments. @@ -14,6 +14,7 @@ The test execution process is divided into modular components that can be easily - **`init.sh`** - Environment initialization and secure AWS/S3 configuration - **`git-clone.sh`** - Repository cloning and extraction (clears Git token) +- **`render-environment-configuration.sh`** - Renders `/environment-configuration/environment-configuration-template.json` in the clone: substitutes `${VAR_NAME}` from the pod env, writes `/tmp/clone/environment-configuration.json`, exports `ATP_ENVGENE_CONFIGURATION` and `ENV_SYSTEMS`. Unset placeholders stay in the file (warning only). Call after `clone_repository`. - **`runtime-setup.sh`** - Runtime-specific environment setup - **`test-runner.sh`** - Test execution and results collection (clears sensitive vars) - **`upload-monitor.sh`** - Event-based upload monitoring and finalization @@ -174,4 +175,4 @@ ENTRYPOINT ["/entrypoint.sh"] 1. Create runtime-specific setup script in `runtimes/` 2. Create runtime-specific test runner if needed 3. Update Dockerfile to copy appropriate modules -4. Test with your specific runtime environment \ No newline at end of file +4. Test with your specific runtime environment \ No newline at end of file diff --git a/email-notification/README-email-notification-json.md b/email-notification/README-email-notification-json.md index 6ec2487..59b5564 100644 --- a/email-notification/README-email-notification-json.md +++ b/email-notification/README-email-notification-json.md @@ -119,7 +119,7 @@ echo "$json_content" # Or use exported variables echo "JSON file: $JSON_FILE" - +echo "Content: $GENERATED_JSON" # File will be saved to: ../email-notification-generated/email-notification-results-generated.json ``` diff --git a/email-notification/calculate-email-notification-variables.sh b/email-notification/calculate-email-notification-variables.sh index 445b171..b98ddc5 100644 --- a/email-notification/calculate-email-notification-variables.sh +++ b/email-notification/calculate-email-notification-variables.sh @@ -9,8 +9,6 @@ # - jq (for JSON parsing) # - bc (for floating point calculations) -set -eo pipefail - # Logging functions log_info() { echo "ℹ️ $1" @@ -37,13 +35,13 @@ ALLURE_RESULTS_DIR="${1:-/tmp/clone/allure-results}" # Check if allure-results directory exists if [ ! -d "$ALLURE_RESULTS_DIR" ]; then log_error "Allure results directory not found: $ALLURE_RESULTS_DIR" - exit 1 + return 1 fi # Check if jq is available if ! command -v jq &> /dev/null; then log_error "jq is required but not installed. Please install jq to parse JSON files." - exit 1 + return 1 fi # Check if bc is available, if not we'll use awk for calculations @@ -105,7 +103,7 @@ done # Calculate pass rate if [ $total_tests -eq 0 ]; then log_error "No test results found in $ALLURE_RESULTS_DIR" - exit 1 + return 1 fi # Calculate pass rate as percentage (passed / total * 100) @@ -138,8 +136,8 @@ export TEST_FAILED_COUNT="$failed_tests" export TEST_SKIPPED_COUNT="$skipped_tests" export TEST_OVERALL_STATUS="$overall_status" -TEST_DETAILS_STRING="" # Create test details string +TEST_DETAILS_STRING="" for test_detail in "${test_details[@]}"; do if [ -n "$TEST_DETAILS_STRING" ]; then TEST_DETAILS_STRING="$TEST_DETAILS_STRING\n$test_detail" @@ -147,6 +145,7 @@ for test_detail in "${test_details[@]}"; do TEST_DETAILS_STRING="$test_detail" fi done +export TEST_DETAILS_STRING="$TEST_DETAILS_STRING" # Display summary echo "" diff --git a/email-notification/generate-email-notification-file.sh b/email-notification/generate-email-notification-file.sh index 350fc30..15a4a24 100644 --- a/email-notification/generate-email-notification-file.sh +++ b/email-notification/generate-email-notification-file.sh @@ -34,7 +34,8 @@ generate_email_notification_file() { } # Get script directory - local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + local SCRIPT_DIR + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Set default values if not provided if [ -z "$template_file" ]; then @@ -45,7 +46,8 @@ generate_email_notification_file() { local allure_results_dir="/tmp/clone/allure-results" # Generate output file name based on template name - local template_basename=$(basename "$template_file" .txt) + local template_basename + template_basename=$(basename "$template_file" .txt) # Create email-notification-generated directory one level up local output_dir="/tmp/clone/scripts/email-notification-generated" @@ -76,16 +78,22 @@ generate_email_notification_file() { EXECUTION_DATE="${EXECUTION_DATE:-$(date '+%Y-%m-%d %H:%M:%S')}" TEST_COVERAGE="${TEST_COVERAGE:-100.00}" ATP_REPORT_VIEW_UI_URL="${ATP_REPORT_VIEW_UI_URL:-https://example.com}" - ALLURE_REPORT_URL="${ATP_REPORT_VIEW_UI_URL}/Report/${ENVIRONMENT_NAME}/${CURRENT_DATE}/${CURRENT_TIME}/allure-report/index.html" + if [[ "${ATP_REPORT_VIEW_UI_URL}" == Test\ not\ started* ]]; then + ALLURE_REPORT_URL="${ATP_REPORT_VIEW_UI_URL}" + else + ALLURE_REPORT_URL="${ATP_REPORT_VIEW_UI_URL}/Report/${ENVIRONMENT_NAME}/${CURRENT_DATE}/${CURRENT_TIME}/allure-report/index.html" + fi TIMESTAMP="${TIMESTAMP:-$(date '+%Y-%m-%d %H:%M:%S UTC')}" # Read template content - local template_content=$(cat "$template_file") + local template_content + template_content=$(cat "$template_file") log_info "Replacing placeholders with actual values..." # Replace placeholders and handle conditional blocks using awk - local message_content=$(echo "$template_content" | awk -v overall_status="$TEST_OVERALL_STATUS" \ + local message_content + message_content=$(cho "$template_content" | awk -v overall_status="$TEST_OVERALL_STATUS" \ -v pass_rate="$TEST_PASS_RATE" \ -v total_count="$TEST_TOTAL_COUNT" \ -v passed_count="$TEST_PASSED_COUNT" \ @@ -207,4 +215,4 @@ generate_email_notification_file() { # Return the message content # echo "$message_content" -} \ No newline at end of file +} diff --git a/email-notification/generate-email-notification-json.sh b/email-notification/generate-email-notification-json.sh index 4dfa401..5dee041 100644 --- a/email-notification/generate-email-notification-json.sh +++ b/email-notification/generate-email-notification-json.sh @@ -10,8 +10,6 @@ # Dependencies: # - calculate-email-notification-variables.sh (for test statistics) -set -eo pipefail - # Function to generate email notification JSON results generate_email_notification_json() { # Logging functions @@ -46,6 +44,7 @@ generate_email_notification_json() { log_info "Generating JSON results file" # Calculate pass rate and test details + # shellcheck source=/home/runner/work/qubership-testing-platform-common-scripts/qubership-testing-platform-common-scripts/scripts/email-notification/calculate-email-notification-variables.sh source "$SCRIPT_DIR/calculate-email-notification-variables.sh" "$allure_results_dir" # Calculate additional metrics @@ -60,7 +59,11 @@ generate_email_notification_json() { EXECUTION_DATE="${EXECUTION_DATE:-$(date '+%Y-%m-%d %H:%M:%S')}" TEST_COVERAGE="${TEST_COVERAGE:-100.00}" ATP_REPORT_VIEW_UI_URL="${ATP_REPORT_VIEW_UI_URL:-https://example.com}" - ALLURE_REPORT_URL="${ATP_REPORT_VIEW_UI_URL}/Report/${ENVIRONMENT_NAME}/${CURRENT_DATE}/${CURRENT_TIME}/allure-report/index.html" + if [[ "${ATP_REPORT_VIEW_UI_URL}" == Test\ not\ started* ]]; then + ALLURE_REPORT_URL="${ATP_REPORT_VIEW_UI_URL}" + else + ALLURE_REPORT_URL="${ATP_REPORT_VIEW_UI_URL}/Report/${ENVIRONMENT_NAME}/${CURRENT_DATE}/${CURRENT_TIME}/allure-report/index.html" + fi TIMESTAMP="${TIMESTAMP:-$(date '+%Y-%m-%d %H:%M:%S UTC')}" log_info "Building JSON structure..." @@ -237,11 +240,10 @@ generate_email_notification_json() { # Export the JSON content as environment variable for use in other scripts + export GENERATED_JSON="$json_content" export JSON_FILE="$output_file" - log_info "Environment variables exported: JSON_FILE" - echo "$output_file" - return 0 + log_info "Environment variables exported: GENERATED_JSON, JSON_FILE" # Return the JSON content # echo "$json_content" diff --git a/error-handler.sh b/error-handler.sh new file mode 100644 index 0000000..a728e14 --- /dev/null +++ b/error-handler.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Centralized error handler and exit-trap for graceful pod termination. +# +# fail() — logs the error, stores its message, and exits 1. +# finalize_once() — the single EXIT trap: writes error-state JSON when rc≠0, +# runs all cleanup, then always exits 0 so Argo does not hang. +# +# Register the trap in entrypoint.sh AFTER all scripts are sourced: +# trap 'finalize_once' EXIT + +# Stores the fatal error message so finalize_once can embed it in the error JSON. +FAIL_MESSAGE="" +FINALIZE_DONE=false + +fail() { + local error_message="${1:-Unknown error}" + echo "❌ FATAL: $error_message" + echo "⚠️ Delegating cleanup to EXIT trap (finalize_once)." + FAIL_MESSAGE="$error_message" + # Catched by EXIT trap and goes to finalize_once() + exit 1 +} + +#shellcheck disable=SC2329 +finalize_once() { + local rc=$? + + if [ "$FINALIZE_DONE" != "true" ]; then + FINALIZE_DONE=true + echo "🔄 EXIT trap triggered with rc=$rc" + + set +e + + # When fail() triggered the exit, write a minimal error-state JSON so + # downstream pipeline stages (e.g. "get ATP report file") receive a valid + # FAILED payload instead of finding no file at all. + if [ "$rc" -ne 0 ]; then + echo "⚠️ Non-zero exit detected — pre-seeding error-state variables for generate_email_notification_json." + # Pre-export the variables that generate_email_notification_json reads. + # calculate-email-notification-variables.sh will bail early (no allure results), + # leaving these values intact so the downstream JSON reflects the fatal error. + export TEST_OVERALL_STATUS="FAILED" + export TEST_PASS_RATE=0 + export TEST_PASS_RATE_ROUNDED=0 + export TEST_TOTAL_COUNT=0 + export TEST_PASSED_COUNT=0 + export TEST_FAILED_COUNT=0 + export TEST_SKIPPED_COUNT=0 + export TEST_COVERAGE=0 + export TEST_DETAILS_STRING="" + export EXECUTION_DATE + EXECUTION_DATE="$(date '+%Y-%m-%d %H:%M:%S')" + export TIMESTAMP + TIMESTAMP="$(date '+%Y-%m-%d %H:%M:%S UTC')" + export ALLURE_REPORT_URL="Test not started. Please check the logs for more details. $FAIL_MESSAGE. " + export ATP_REPORT_VIEW_UI_URL="Test not started. Please check the logs for more details. $FAIL_MESSAGE. " + fi + + generate_email_notification_json || true + save_native_report "$TMP_DIR/${NATIVE_REPORT_DIR:-playwright-report}" || true + finalize_upload || true + sleep 15 + + set -e + fi + + # Always exit 0 so Argo/Kubernetes does not treat this pod as failed and hang. + exit 0 +} diff --git a/git-clone.sh b/git-clone.sh index 2d51297..778994c 100644 --- a/git-clone.sh +++ b/git-clone.sh @@ -14,25 +14,25 @@ clone_repository() { # ============================================ if [ -z "${ATP_TESTS_GIT_TOKEN:-}" ]; then echo "❌ ERROR: ATP_TESTS_GIT_TOKEN is not set (required to download repository archive)" - exit 1 + return 1 fi if [ -z "${ATP_TESTS_GIT_REPO_URL:-}" ]; then echo "❌ ERROR: ATP_TESTS_GIT_REPO_URL is not set" - exit 1 + return 1 fi if [ -z "${ATP_TESTS_GIT_REPO_BRANCH:-}" ]; then echo "❌ ERROR: ATP_TESTS_GIT_REPO_BRANCH is not set" - exit 1 + return 1 fi if [ -z "${TMP_DIR:-}" ]; then echo "❌ ERROR: TMP_DIR is not set" - exit 1 + return 1 fi # Basic URL sanity check (no whitespace, http/https) if [[ "$ATP_TESTS_GIT_REPO_URL" =~ [[:space:]] ]] || [[ ! "$ATP_TESTS_GIT_REPO_URL" =~ ^https?:// ]]; then echo "❌ ERROR: ATP_TESTS_GIT_REPO_URL is invalid URL: $ATP_TESTS_GIT_REPO_URL" - exit 1 + return 1 fi # Strip .git from URL and extract repo name @@ -152,7 +152,7 @@ clone_repository() { # For non-HTTP curl failures, stop here (HTTP_CODE may be empty/undefined). if [ "$CURL_EXIT_CODE" -ne 22 ]; then - exit 1 + return 1 fi fi @@ -164,12 +164,12 @@ clone_repository() { 000) echo "❌ ERROR: No HTTP response received from server (HTTP 000)." echo " Check network connectivity and URL: $ARCHIVE_URL" - exit 1 + return 1 ;; 401|403) echo "❌ ERROR: Authentication failed (HTTP $HTTP_CODE)." echo " Check that ATP_TESTS_GIT_TOKEN is valid and has access to the repository." - exit 1 + return 1 ;; 404) echo "❌ ERROR: Repository or branch not found (HTTP 404)." @@ -177,22 +177,22 @@ clone_repository() { echo " - URL: $ATP_TESTS_GIT_REPO_URL" echo " - Branch: $ATP_TESTS_GIT_REPO_BRANCH" echo " - Archive URL: $ARCHIVE_URL" - exit 1 + return 1 ;; 429) echo "❌ ERROR: Rate limited by the server (HTTP 429)." echo " Try again later or reduce request frequency." - exit 1 + return 1 ;; 5??) echo "❌ ERROR: Server error while downloading archive (HTTP $HTTP_CODE)." echo " The Git server may be temporarily unavailable." - exit 1 + return 1 ;; *) echo "❌ ERROR: Failed to download repository archive (HTTP $HTTP_CODE, curl exit code: $CURL_EXIT_CODE)." echo " Archive URL: $ARCHIVE_URL" - exit 1 + return 1 ;; esac fi @@ -202,7 +202,7 @@ clone_repository() { # ============================================ if [ ! -f "$ZIP_PATH" ] || [ ! -s "$ZIP_PATH" ]; then echo "❌ ERROR: Downloaded file is missing or empty: $ZIP_PATH" - exit 1 + return 1 fi if command -v file >/dev/null 2>&1; then @@ -217,7 +217,7 @@ clone_repository() { echo "❌ ERROR: Downloaded repository is not recognized as a zip archive." fi echo " File type: $FILE_TYPE" - exit 1 + return 1 fi else echo "⚠️ 'file' command not available; skipping zip magic-byte validation." @@ -226,17 +226,17 @@ clone_repository() { if command -v unzip >/dev/null 2>&1; then if ! unzip -t "$ZIP_PATH" > /dev/null 2>&1; then echo "❌ ERROR: Archive integrity test failed (unzip -t). Please retry the operation." - exit 1 + return 1 fi else echo "❌ ERROR: 'unzip' command is not available; cannot validate/extract archive." - exit 1 + return 1 fi echo "📦 Unzipping..." unzip -q "$ZIP_PATH" -d "$TMP_DIR" || { echo "❌ Failed to unzip repository archive" - exit 1 + return 1 } extracted_dir="$TMP_DIR/${REPO_NAME}-${GIT_BRANCH_CLEANED}" @@ -247,14 +247,14 @@ clone_repository() { fetch_by_clone_with_submodules || { echo "❌ Failed to clone repository with submodules" - exit 1 + return 1 } else shopt -s dotglob mv "$extracted_dir"/* "$TMP_DIR" || { shopt -u dotglob echo "❌ Failed to move extracted repository contents" - exit 1 + return 1 } shopt -u dotglob @@ -274,10 +274,10 @@ clone_repository() { echo "✅ Validation successful. Found 'collections/' directory in the repo." else echo "❌ ERROR: Neither 'app/' nor 'tests/' nor 'collections/' directory nor 'postman_collection' file found in the cloned repo!" - exit 1 + return 1 fi - cd "$TMP_DIR" || exit 1 + cd "$TMP_DIR" || return 1 if [ -d "$TMP_DIR/app" ]; then echo "📋 Contents of $TMP_DIR/app directory:" diff --git a/init.sh b/init.sh index f89a561..87006bc 100644 --- a/init.sh +++ b/init.sh @@ -15,11 +15,11 @@ init_environment() { # Configure AWS S3 parameters (required) - using local variables for security if [[ -z "${ATP_STORAGE_USERNAME}" ]]; then echo "❌ ATP_STORAGE_USERNAME is required but not set" - exit 1 + return 1 fi if [[ -z "${ATP_STORAGE_PASSWORD}" ]]; then echo "❌ ATP_STORAGE_PASSWORD is required but not set" - exit 1 + return 1 fi # Store credentials in local variables (not exported to environment) diff --git a/render-environment-configuration.sh b/render-environment-configuration.sh new file mode 100644 index 0000000..c80342e --- /dev/null +++ b/render-environment-configuration.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Renders EnvironmentConfiguration template using pod environment variables: +# ${VAR_NAME} -> value of $VAR_NAME (must be set; unset vars keep placeholder, warn only). + +render_environment_configuration() { + local template_path="${TMP_DIR}/environment-configuration/${ENV_CONFIGURATION_TEMPLATE_FILENAME:-environment-configuration-template.json}" + local output_path="${TMP_DIR}/environment-configuration.json" + local template_content rendered_content placeholders placeholder var_name var_value + local missing_vars=() + + if [ -z "${TMP_DIR:-}" ]; then + echo "❌ ERROR: TMP_DIR is not set; cannot render environment configuration" + return 1 + fi + + if [ ! -f "$template_path" ]; then + echo "ℹ️ Environment configuration template not found: $template_path" + return 0 + fi + + echo "🔄 Environment configuration template found. Starting environment systems rendering..." + + template_content="$(cat "$template_path")" + + # JSON format validation + if ! command -v jq >/dev/null 2>&1; then + echo "❌ ERROR: Cannot validate JSON format. 'jq' is not available." + return 1 + fi + + if ! printf '%s' "$template_content" | jq empty >/dev/null 2>&1; then + echo "❌ ERROR: Invalid JSON format in environment configuration template: $template_path" + return 1 + fi + + rendered_content="$template_content" + + placeholders="$(printf '%s' "$template_content" | grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*\}' | sort -u || true)" + + if [ -n "$placeholders" ]; then + while IFS= read -r placeholder; do + [ -z "$placeholder" ] && continue + var_name="${placeholder:2:${#placeholder}-3}" + + if [ -n "${!var_name+x}" ]; then + var_value="${!var_name}" + rendered_content="${rendered_content//"$placeholder"/$var_value}" + else + missing_vars+=("$var_name") + echo "⚠️ Environment variable '$var_name' is not set. Leaving placeholder $placeholder as-is." + fi + done <<< "$placeholders" + fi + + printf '%s' "$rendered_content" > "$output_path" + export ATP_ENVGENE_CONFIGURATION="$rendered_content" + + if [ "${#missing_vars[@]}" -gt 0 ]; then + echo "⚠️ Rendering completed with missing variables: ${missing_vars[*]}" + fi + + # Debug mode output + case "${DEBUG_MODE:-false}" in + 1|true|yes|on) + echo "🧪 DEBUG: Rendered environment configuration content:" + printf '%s\n' "$rendered_content" + ;; + esac + + echo "✅ Environment configuration rendered and saved to: $output_path" + echo "✅ Exported rendered configuration to ATP_ENVGENE_CONFIGURATION" +} diff --git a/test-runner-bruno.sh b/test-runner-bruno.sh new file mode 100644 index 0000000..17c7d98 --- /dev/null +++ b/test-runner-bruno.sh @@ -0,0 +1,334 @@ +#!/bin/bash + +run_collection_body() { + + collection_dir="$1" + collection_path="${TMP_DIR}/${collection_dir}" + + + if [ -n "$BRUNO_FOLDERS_STR" ]; then + mapfile -t BRUNO_FOLDERS_ARRAY <<< "$BRUNO_FOLDERS_STR" + else + BRUNO_FOLDERS_ARRAY=() + fi + + echo "➡️ Processing collection: $collection_path" + + if [ -d "$collection_path" ]; then + collection_name=$(basename "$collection_dir") + bruno_report_path="${PATH_TO_ATTACHMENTS_DIR}/${collection_name}-result.json" + raw_log_path="${PATH_TO_ATTACHMENTS_DIR}/${collection_name}.raw.log" + + collection_start_ts=$(date +%s) + echo "🚀 START collection=$collection_name pid=$$ time=$(date '+%H:%M:%S')" + + pushd "$collection_path" > /dev/null || return 1 + + RESOLVED_FOLDERS=() + + if [ ${#BRUNO_FOLDERS_ARRAY[@]} -gt 0 ]; then + + for folder in "${BRUNO_FOLDERS_ARRAY[@]}"; do + found_any=false + + while IFS= read -r found; do + echo "✔ Found folder in $collection_name: $found" + RESOLVED_FOLDERS+=("$found") + found_any=true + done < <(find . -maxdepth 5 -type d -name "$folder" -not -path "*/.git/*" -not -path "*/node_modules/*") + + if [ "$found_any" = false ]; then + echo "⚠ Folder not found in $collection_name: $folder" + fi + done + + fi + + + if [ ${#BRUNO_FOLDERS_ARRAY[@]} -eq 0 ]; then + + echo "➡ Running full collection" + echo "▶ BRUNO RUN START collection=$collection_name pid=$$ mode=full time=$(date '+%H:%M:%S')" + + COLLECTION_TIMEOUT="${COLLECTION_TIMEOUT:-3600}" + + if timeout --signal=TERM --kill-after=30s "${COLLECTION_TIMEOUT}s" \ + ${BRU_BIN}/bru.js run ${BRUNO_FLAGS_CLI} \ + --env "${BRUNO_ENV_STR}" \ + ${BRUNO_ENV_VARS_CLI} \ + --reporter-json "${bruno_report_path}" \ + 2>&1 | tee "${raw_log_path}"; then + echo "✅ SUCCESS: $collection_name" + else + rc=${PIPESTATUS[0]} + echo "❌ FAILED: $collection_name rc=$rc" + echo "----- LAST 200 LINES: $collection_name -----" + tail -n 200 "${raw_log_path}" || true + echo "--------------------------------------------" + touch "${TMP_DIR}/.collection_failed" + fi + + echo "◀ BRUNO RUN END collection=$collection_name pid=$$ time=$(date '+%H:%M:%S')" + else + + if [ ${#RESOLVED_FOLDERS[@]} -eq 0 ]; then + echo "⚠ No matching folders found — skipping collection" + popd > /dev/null || return 1 + uuid=$(cat /proc/sys/kernel/random/uuid) + + cat > "$PATH_TO_ALLURE_RESULTS/${uuid}-result.json" <&1 | tee "${raw_log_path}"; then + echo "✅ SUCCESS: $collection_name" + else + rc=${PIPESTATUS[0]} + echo "❌ FAILED: $collection_name rc=$rc" + echo "----- LAST 200 LINES: $collection_name -----" + tail -n 200 "${raw_log_path}" || true + echo "--------------------------------------------" + touch "${TMP_DIR}/.collection_failed" + fi + + echo "◀ BRUNO RUN END collection=$collection_name pid=$$ time=$(date '+%H:%M:%S')" + fi + + popd > /dev/null || return 1 + + collection_end_ts=$(date +%s) + + echo "🏁 BRUNO EXECUTION FINISHED =$collection_name pid=$$ duration=$((collection_end_ts-collection_start_ts))s time=$(date '+%H:%M:%S')" + + if [ -f "$bruno_report_path" ]; then + echo "📦 Parsing report: $bruno_report_path" + + count=$(jq 'if type=="array" then (if (.[0]?|type)=="object" and (.[0]?|has("results")) then ([.[].results[]]|length) else length end) elif type=="object" and has("results") then (.results|length) else 0 end' "$bruno_report_path") + echo "📊 $collection_name → $count tests" + printf "%s,%s\n" "$collection_name" "$count" >> "$TMP_DIR/tests_count.csv" + node /scripts/tools/bruno-to-allure.js \ + "$bruno_report_path" \ + "$PATH_TO_ALLURE_RESULTS" \ + "$collection_name" + echo "✅ COLLECTION FULLY FINISHED collection=$collection_name pid=$$ time=$(date '+%H:%M:%S')" + else + + echo "⚠ Bruno report missing — writing broken test to Allure" + + uuid=$(cat /proc/sys/kernel/random/uuid) + + cat > "$PATH_TO_ALLURE_RESULTS/${uuid}-result.json" < "$skipped_file" < "$TMP_DIR/tests_count.csv" + + { + echo "BRUNO_ENV=$BRUNO_ENV_STR" + + while IFS= read -r key; do + case "$key" in + *_URL|*_LOGIN|*_PASSWORD|NAMESPACE|SERVER_HOSTNAME) + printf '%s=%s\n' "$key" "${!key}" + ;; + esac + done < <(compgen -e | sort) + } > "$PATH_TO_ALLURE_RESULTS/environment.properties" + + echo "Env vars exported to Bruno child processes:" + while IFS= read -r key; do + case "$key" in + *_URL|*_LOGIN|*_PASSWORD|NAMESPACE|SERVER_HOSTNAME) + export "${key?}" + echo " - $key" + ;; + esac + done < <(compgen -e) + + export -f run_collection_body + + export TMP_DIR + export PATH_TO_ATTACHMENTS_DIR + export PATH_TO_ALLURE_RESULTS + export BRU_BIN + export BRUNO_ENV_STR + export BRUNO_ENV_VARS_CLI + export BRUNO_FLAGS_CLI + + if [ ${#BRUNO_FOLDERS_ARRAY[@]} -gt 0 ]; then + BRUNO_FOLDERS_STR=$(printf "%s\n" "${BRUNO_FOLDERS_ARRAY[@]}") + else + BRUNO_FOLDERS_STR="" + fi + +export BRUNO_FOLDERS_STR + + PARALLELISM=${PARALLELISM:-4} + echo "Collections to run:" + printf "%s\n" "${BRUNO_COLLECTIONS_ARRAY[@]}" + echo "Total collections: ${#BRUNO_COLLECTIONS_ARRAY[@]}" + + echo "⚡ Running ${#BRUNO_COLLECTIONS_ARRAY[@]} collections with parallelism=$PARALLELISM" + + printf "%s\n" "${BRUNO_COLLECTIONS_ARRAY[@]}" > "$PATH_TO_ALLURE_RESULTS/collections.txt" + + parallel_start_ts=$(date +%s) + echo "✅ PARALLEL PHASE START time=$(date '+%H:%M:%S')" + + running_jobs=0 + active_collection_pids=() + + wait_for_collection_slot() { + while true; do + for idx in "${!active_collection_pids[@]}"; do + local pid="${active_collection_pids[$idx]}" + + if ! kill -0 "$pid" 2>/dev/null; then + wait "$pid" || true + unset 'active_collection_pids[idx]' + active_collection_pids=("${active_collection_pids[@]}") + return 0 + fi + done + + sleep 1 + done + } + + for collection in "${BRUNO_COLLECTIONS_ARRAY[@]}"; do + bash -c 'run_collection_body "$1"' _ "$collection" & + active_collection_pids+=("$!") + running_jobs=$((running_jobs + 1)) + + if [ "$running_jobs" -ge "$PARALLELISM" ]; then + wait_for_collection_slot + running_jobs=$((running_jobs - 1)) + fi + done + + while [ "$running_jobs" -gt 0 ]; do + wait_for_collection_slot + running_jobs=$((running_jobs - 1)) + done + + parallel_end_ts=$(date +%s) + + echo "✅ PARALLEL PHASE END time=$(date '+%H:%M:%S') took=$((parallel_end_ts-parallel_start_ts))s" + + + echo "==== TEST COUNT BY COLLECTION ====" + sort "$TMP_DIR/tests_count.csv" + echo "-----------------------------------------" + + if [ -f "${TMP_DIR}/.collection_failed" ]; then + return 1 + fi + return 0 +} diff --git a/test-runner.sh b/test-runner.sh index 3427419..dc9fe86 100644 --- a/test-runner.sh +++ b/test-runner.sh @@ -3,16 +3,13 @@ run_tests() { echo "▶ Starting test execution..." - set -o pipefail - # shellcheck disable=SC1091 if [ -f "/app/scripts/upload-monitor.sh" ]; then source "/app/scripts/upload-monitor.sh" elif [ -f "/scripts/upload-monitor.sh" ]; then source "/scripts/upload-monitor.sh" else - echo "❌ upload-monitor.sh not found!" - exit 1 + fail "upload-monitor.sh not found" fi echo "📁 Creating Allure results directory..." @@ -24,13 +21,23 @@ run_tests() { echo "🚀 Running test suite..." if [ -f "./start_tests.sh" ]; then - chmod +x "./start_tests.sh" - ./start_tests.sh - TEST_EXIT_CODE=$? + echo "🚀 Running test suite..." + chmod +x start_tests.sh + ./start_tests.sh || TEST_EXIT_CODE=$? + + TEST_EXIT_CODE=${TEST_EXIT_CODE:-0} + if [ $TEST_EXIT_CODE -ne 0 ]; then + fail "Test suite failed with code: $TEST_EXIT_CODE" + else + echo "✅ Test suite completed successfully" + fi elif [ -d "./collections" ]; then echo "ℹ️ collections/ detected — running Bruno runner" run_bruno_from_test_params TEST_EXIT_CODE=$? + if [ $TEST_EXIT_CODE -ne 0 ]; then + fail "Bruno runner failed with code: $TEST_EXIT_CODE" + fi else echo "❌ Neither start_tests.sh nor collections/ directory found" exit 1 @@ -41,336 +48,4 @@ run_tests() { echo "✅ Test execution completed" return "$TEST_EXIT_CODE" -} - - -run_collection_body() { - - collection_dir="$1" - collection_path="${TMP_DIR}/${collection_dir}" - - - if [ -n "$BRUNO_FOLDERS_STR" ]; then - mapfile -t BRUNO_FOLDERS_ARRAY <<< "$BRUNO_FOLDERS_STR" - else - BRUNO_FOLDERS_ARRAY=() - fi - - echo "➡️ Processing collection: $collection_path" - - if [ -d "$collection_path" ]; then - collection_name=$(basename "$collection_dir") - bruno_report_path="${PATH_TO_ATTACHMENTS_DIR}/${collection_name}-result.json" - raw_log_path="${PATH_TO_ATTACHMENTS_DIR}/${collection_name}.raw.log" - - collection_start_ts=$(date +%s) - echo "🚀 START collection=$collection_name pid=$$ time=$(date '+%H:%M:%S')" - - pushd "$collection_path" > /dev/null || return 1 - - RESOLVED_FOLDERS=() - - if [ ${#BRUNO_FOLDERS_ARRAY[@]} -gt 0 ]; then - - for folder in "${BRUNO_FOLDERS_ARRAY[@]}"; do - found_any=false - - while IFS= read -r found; do - echo "✔ Found folder in $collection_name: $found" - RESOLVED_FOLDERS+=("$found") - found_any=true - done < <(find . -maxdepth 5 -type d -name "$folder" -not -path "*/.git/*" -not -path "*/node_modules/*") - - if [ "$found_any" = false ]; then - echo "⚠ Folder not found in $collection_name: $folder" - fi - done - - fi - - - if [ ${#BRUNO_FOLDERS_ARRAY[@]} -eq 0 ]; then - - echo "➡ Running full collection" - echo "▶ BRUNO RUN START collection=$collection_name pid=$$ mode=full time=$(date '+%H:%M:%S')" - - COLLECTION_TIMEOUT="${COLLECTION_TIMEOUT:-3600}" - - if timeout --signal=TERM --kill-after=30s "${COLLECTION_TIMEOUT}s" \ - ${BRU_BIN}/bru.js run ${BRUNO_FLAGS_CLI} \ - --env "${BRUNO_ENV_STR}" \ - ${BRUNO_ENV_VARS_CLI} \ - --reporter-json "${bruno_report_path}" \ - 2>&1 | tee "${raw_log_path}"; then - echo "✅ SUCCESS: $collection_name" - else - rc=${PIPESTATUS[0]} - echo "❌ FAILED: $collection_name rc=$rc" - echo "----- LAST 200 LINES: $collection_name -----" - tail -n 200 "${raw_log_path}" || true - echo "--------------------------------------------" - TOTAL_FAILED=1 - fi - - echo "◀ BRUNO RUN END collection=$collection_name pid=$$ time=$(date '+%H:%M:%S')" - else - - if [ ${#RESOLVED_FOLDERS[@]} -eq 0 ]; then - echo "⚠ No matching folders found — skipping collection" - popd > /dev/null - uuid=$(cat /proc/sys/kernel/random/uuid) - - cat > "$PATH_TO_ALLURE_RESULTS/${uuid}-result.json" <&1 | tee "${raw_log_path}"; then - echo "✅ SUCCESS: $collection_name" - else - rc=${PIPESTATUS[0]} - echo "❌ FAILED: $collection_name rc=$rc" - echo "----- LAST 200 LINES: $collection_name -----" - tail -n 200 "${raw_log_path}" || true - echo "--------------------------------------------" - TOTAL_FAILED=1 - fi - - echo "◀ BRUNO RUN END collection=$collection_name pid=$$ time=$(date '+%H:%M:%S')" - fi - - popd > /dev/null || return 1 - - collection_end_ts=$(date +%s) - - echo "🏁 BRUNO EXECUTION FINISHED =$collection_name pid=$$ duration=$((collection_end_ts-collection_start_ts))s time=$(date '+%H:%M:%S')" - - if [ -f "$bruno_report_path" ]; then - echo "📦 Parsing report: $bruno_report_path" - - count=$(jq 'if type=="array" then (if (.[0]?|type)=="object" and (.[0]?|has("results")) then ([.[].results[]]|length) else length end) elif type=="object" and has("results") then (.results|length) else 0 end' "$bruno_report_path") - echo "📊 $collection_name → $count tests" - printf "%s,%s\n" "$collection_name" "$count" >> "$TMP_DIR/tests_count.csv" - node /scripts/tools/bruno-to-allure.js \ - "$bruno_report_path" \ - "$PATH_TO_ALLURE_RESULTS" \ - "$collection_name" - echo "✅ COLLECTION FULLY FINISHED collection=$collection_name pid=$$ time=$(date '+%H:%M:%S')" - else - - echo "⚠ Bruno report missing — writing broken test to Allure" - - uuid=$(cat /proc/sys/kernel/random/uuid) - - cat > "$PATH_TO_ALLURE_RESULTS/${uuid}-result.json" < "$skipped_file" < "$TMP_DIR/tests_count.csv" - - { - echo "BRUNO_ENV=$BRUNO_ENV_STR" - - while IFS= read -r key; do - case "$key" in - *_URL|*_LOGIN|*_PASSWORD|NAMESPACE|SERVER_HOSTNAME) - printf '%s=%s\n' "$key" "${!key}" - ;; - esac - done < <(compgen -e | sort) - } > "$PATH_TO_ALLURE_RESULTS/environment.properties" - - echo "Env vars exported to Bruno child processes:" - while IFS= read -r key; do - case "$key" in - *_URL|*_LOGIN|*_PASSWORD|NAMESPACE|SERVER_HOSTNAME) - export "$key" - echo " - $key" - ;; - esac - done < <(compgen -e) - - TOTAL_FAILED=0 - export -f run_collection_body - - export TMP_DIR - export PATH_TO_ATTACHMENTS_DIR - export PATH_TO_ALLURE_RESULTS - export BRU_BIN - export BRUNO_ENV_STR - export BRUNO_ENV_VARS_CLI - export BRUNO_FLAGS_CLI - - if [ ${#BRUNO_FOLDERS_ARRAY[@]} -gt 0 ]; then - BRUNO_FOLDERS_STR=$(printf "%s\n" "${BRUNO_FOLDERS_ARRAY[@]}") - else - BRUNO_FOLDERS_STR="" - fi - -export BRUNO_FOLDERS_STR - - PARALLELISM=${PARALLELISM:-4} - echo "Collections to run:" - printf "%s\n" "${BRUNO_COLLECTIONS_ARRAY[@]}" - echo "Total collections: ${#BRUNO_COLLECTIONS_ARRAY[@]}" - - echo "⚡ Running ${#BRUNO_COLLECTIONS_ARRAY[@]} collections with parallelism=$PARALLELISM" - - printf "%s\n" "${BRUNO_COLLECTIONS_ARRAY[@]}" > "$PATH_TO_ALLURE_RESULTS/collections.txt" - - parallel_start_ts=$(date +%s) - echo "✅ PARALLEL PHASE START time=$(date '+%H:%M:%S')" - - running_jobs=0 - active_collection_pids=() - - wait_for_collection_slot() { - while true; do - for idx in "${!active_collection_pids[@]}"; do - local pid="${active_collection_pids[$idx]}" - - if ! kill -0 "$pid" 2>/dev/null; then - wait "$pid" || true - unset 'active_collection_pids[idx]' - active_collection_pids=("${active_collection_pids[@]}") - return 0 - fi - done - - sleep 1 - done - } - - for collection in "${BRUNO_COLLECTIONS_ARRAY[@]}"; do - bash -c 'run_collection_body "$1"' _ "$collection" & - active_collection_pids+=("$!") - running_jobs=$((running_jobs + 1)) - - if [ "$running_jobs" -ge "$PARALLELISM" ]; then - wait_for_collection_slot - running_jobs=$((running_jobs - 1)) - fi - done - - while [ "$running_jobs" -gt 0 ]; do - wait_for_collection_slot - running_jobs=$((running_jobs - 1)) - done - - parallel_end_ts=$(date +%s) - - echo "✅ PARALLEL PHASE END time=$(date '+%H:%M:%S') took=$((parallel_end_ts-parallel_start_ts))s" - - - echo "==== TEST COUNT BY COLLECTION ====" - sort "$TMP_DIR/tests_count.csv" - echo "-----------------------------------------" - - return 0 -} +} \ No newline at end of file diff --git a/tools/bru_tools.sh b/tools/bru_tools.sh index 3170e99..85995ee 100644 --- a/tools/bru_tools.sh +++ b/tools/bru_tools.sh @@ -26,12 +26,12 @@ check_env_var() { if [[ -z "${!var_name:-}" ]]; then if [[ -z "$compute_expr" ]]; then echo "❗Error: Variable $var_name must be specified!" >&2 - exit 1 + return 1 else # Calculate the value if ! computed_value=$(eval "$compute_expr" 2>/dev/null); then echo "❗Error calculating the value for $var_name" >&2 - exit 1 + return 1 fi # Export variable