diff --git a/.github/workflows/add-submodules.yml b/.github/workflows/add-submodules.yml index 09a12f9..032b799 100644 --- a/.github/workflows/add-submodules.yml +++ b/.github/workflows/add-submodules.yml @@ -41,17 +41,13 @@ jobs: run: | set -euo pipefail - # Summary buckets (doc metadata / skips); printed after Step 2. - META_MISSING=() - NO_DOC_PATHS=() - REPO_EXISTS_SKIP=() - # shellcheck source=assets/env.sh source "$GITHUB_WORKSPACE/.github/workflows/assets/env.sh" # shellcheck source=assets/lib.sh source "$GITHUB_WORKSPACE/.github/workflows/assets/lib.sh" init_translation_state + init_add_submodule_summary_buckets begin_phase "$PHASE_SETUP" "Validate inputs and prepare workspace" validate_secrets @@ -193,24 +189,27 @@ jobs: record_submodule_update "$sub" || true else rc=$? - [[ $rc -eq 2 ]] && submodule_fatal=$((submodule_fatal + 1)) + if [[ $rc -eq 2 ]]; then + record_submodule_fatal "$sub" + submodule_fatal=$((submodule_fatal + 1)) + fi fi done - echo "── Summary: Boost library documentation metadata ──" >&2 - echo " Type 1 — Missing or unreadable meta/libraries.json (${#META_MISSING[@]}): $([[ ${#META_MISSING[@]} -eq 0 ]] && echo '(none)' || echo "${META_MISSING[*]}")" >&2 - echo " Type 2 — No doc paths in meta/libraries.json (${#NO_DOC_PATHS[@]}): $([[ ${#NO_DOC_PATHS[@]} -eq 0 ]] && echo '(none)' || echo "${NO_DOC_PATHS[*]}")" >&2 - echo " Type 3 — Target org repo already exists, skipped (${#REPO_EXISTS_SKIP[@]}): $([[ ${#REPO_EXISTS_SKIP[@]} -eq 0 ]] && echo '(none)' || echo "${REPO_EXISTS_SKIP[*]}")" >&2 - - [[ $submodule_fatal -gt 0 ]] && { + # Buckets filled by process_one_submodule. + print_submodule_processing_summary + [[ $submodule_fatal -gt 0 ]] && \ phase_err "$submodule_fatal submodule(s) failed with errors." - end_phase - exit 1 - } end_phase begin_phase "$PHASE_FINALIZE_TRANSLATIONS" "Finalize translations repo" - finalize_translations_repo "$TRANS_DIR" "$LIBS_REF" "${lang_codes_arr[@]}" + finalize_rc=0 + finalize_translations_repo "$TRANS_DIR" "$LIBS_REF" "${lang_codes_arr[@]}" || finalize_rc=$? end_phase + exit_rc=0 + [[ $submodule_fatal -gt 0 ]] && exit_rc=1 + [[ $finalize_rc -ne 0 ]] && exit_rc=$finalize_rc + [[ $exit_rc -ne 0 ]] && exit $exit_rc + echo "Done." >&2 diff --git a/.github/workflows/assets/env.sh b/.github/workflows/assets/env.sh index 2ec4a8a..e6e682b 100644 --- a/.github/workflows/assets/env.sh +++ b/.github/workflows/assets/env.sh @@ -31,8 +31,10 @@ if [[ -z "${_ENV_SH_LOADED:-}" ]]; then ) fi -ORG="${GITHUB_REPOSITORY%%/*}" -TRANSLATIONS_REPO="${GITHUB_REPOSITORY##*/}" +_repo="${GITHUB_REPOSITORY:-}" +ORG="${_repo%%/*}" +TRANSLATIONS_REPO="${_repo##*/}" +unset _repo BOT_NAME="Boost-Translation-CI-Bot" BOT_EMAIL="Boost-Translation-CI-Bot@$ORG.local" diff --git a/.github/workflows/assets/lib.sh b/.github/workflows/assets/lib.sh index 04a8924..5f1fc38 100644 --- a/.github/workflows/assets/lib.sh +++ b/.github/workflows/assets/lib.sh @@ -138,6 +138,7 @@ add_create_tag_workflow() { "$wf_dir/create-tag.yml" cp "$GITHUB_WORKSPACE/.github/workflows/assets/env.sh" \ "$wf_dir/assets/env.sh" + set_git_bot_config "$repo_dir" git -C "$repo_dir" add ".github/workflows/create-tag.yml" ".github/workflows/assets/env.sh" git -C "$repo_dir" commit -m "Add create-tag workflow" } @@ -240,10 +241,11 @@ finalize_translations_local() { finalize_translations_repo() { local dir="$1" libs_ref="$2" shift 2 - local lang_codes_arr=("$@") - finalize_translations_master "$dir" "$libs_ref" + local lang_codes_arr=("$@") lang_code + + finalize_translations_master "$dir" "$libs_ref" || return $? for lang_code in "${lang_codes_arr[@]}"; do - finalize_translations_local "$dir" "$libs_ref" "$lang_code" + finalize_translations_local "$dir" "$libs_ref" "$lang_code" || return $? done } @@ -260,9 +262,17 @@ finalize_translations_repo() { # Submodule names eligible for Weblate per language; only submodules that # passed process_local_branch. Written via record_add_or_update_submodule; # consumed by trigger_weblate (translation.sh). +# +# SUBMODULE_FATAL (indexed array) +# Submodule names that returned fatal (exit 2) from process_one_submodule. +# +# OPEN_PR_SKIP (indexed array) +# Submodule names skipped due to an open translation PR (start-translation local). init_translation_state() { UPDATES=() + SUBMODULE_FATAL=() + OPEN_PR_SKIP=() declare -gA add_or_update=() } @@ -301,6 +311,63 @@ _submodule_in_add_or_update() { return 1 } +_submodule_in_array() { + local name="$1" + shift + local item + for item in "$@"; do + [[ "$item" == "$name" ]] && return 0 + done + return 1 +} + +# Append a fatal submodule name (idempotent on duplicate). +record_submodule_fatal() { + local sub_name="$1" + _submodule_in_array "$sub_name" "${SUBMODULE_FATAL[@]}" && return 0 + SUBMODULE_FATAL+=("$sub_name") +} + +# Summary bucket globals; filled by process_one_submodule before print_submodule_processing_summary. +init_submodule_summary_buckets() { + META_MISSING=() + NO_DOC_PATHS=() + ORG_REPO_MISSING=() +} + +init_add_submodule_summary_buckets() { + META_MISSING=() + NO_DOC_PATHS=() + REPO_EXISTS_SKIP=() +} + +# Print consolidated success / failure / skip summary after the per-submodule loop. +print_submodule_processing_summary() { + local -a processing_errors=() repo_exists_skip=() + local -a meta_missing=() org_repo_missing=() no_doc_paths=() + local sub + + [[ ${META_MISSING+set} ]] && meta_missing=("${META_MISSING[@]}") + [[ ${ORG_REPO_MISSING+set} ]] && org_repo_missing=("${ORG_REPO_MISSING[@]}") + [[ ${NO_DOC_PATHS+set} ]] && no_doc_paths=("${NO_DOC_PATHS[@]}") + [[ ${REPO_EXISTS_SKIP+set} ]] && repo_exists_skip=("${REPO_EXISTS_SKIP[@]}") + + for sub in "${SUBMODULE_FATAL[@]}"; do + _submodule_in_array "$sub" "${meta_missing[@]}" && continue + _submodule_in_array "$sub" "${org_repo_missing[@]}" && continue + processing_errors+=("$sub") + done + + echo "── Submodule processing summary ──" >&2 + echo " Successfully updated (${#UPDATES[@]}): $([[ ${#UPDATES[@]} -eq 0 ]] && echo '(none)' || echo "${UPDATES[*]}")" >&2 + echo " Failed — Type 1, missing meta/libraries.json (${#meta_missing[@]}): $([[ ${#meta_missing[@]} -eq 0 ]] && echo '(none)' || echo "${meta_missing[*]}")" >&2 + echo " Failed — Type 3, org repo missing (${#org_repo_missing[@]}): $([[ ${#org_repo_missing[@]} -eq 0 ]] && echo '(none)' || echo "${org_repo_missing[*]}")" >&2 + echo " Failed — processing error (${#processing_errors[@]}): $([[ ${#processing_errors[@]} -eq 0 ]] && echo '(none)' || echo "${processing_errors[*]}")" >&2 + echo " Skipped — Type 2, no doc paths (${#no_doc_paths[@]}): $([[ ${#no_doc_paths[@]} -eq 0 ]] && echo '(none)' || echo "${no_doc_paths[*]}")" >&2 + echo " Skipped — org repo already exists (${#repo_exists_skip[@]}): $([[ ${#repo_exists_skip[@]} -eq 0 ]] && echo '(none)' || echo "${repo_exists_skip[*]}")" >&2 + echo " Skipped — open translation PR (${#OPEN_PR_SKIP[@]}): $([[ ${#OPEN_PR_SKIP[@]} -eq 0 ]] && echo '(none)' || echo "${OPEN_PR_SKIP[*]}")" >&2 +} + # Append a successfully processed submodule to UPDATES (idempotent on duplicate). record_submodule_update() { local sub_name="$1" @@ -349,6 +416,22 @@ validate_add_or_update_entry() { # ── Parsing helpers ─────────────────────────────────────────────────── +# Emit a compact JSON array of submodule basenames (empty → []). +submodule_names_to_json() { + if [[ $# -eq 0 ]]; then + echo '[]' + return + fi + printf '%s\n' "$@" | jq -R . | jq -s -c . +} + +# Parse JSON array of submodule basenames; one name per line (empty/no-op for []). +parse_submodule_names_json() { + local json="${1:-}" + [[ -z "$json" || "$json" == "[]" ]] && return 0 + jq -r '.[]' <<< "$json" +} + # Parse "[zh_Hans, en]" or "zh_Hans,en" into one code per line. parse_list() { local s="$1" diff --git a/.github/workflows/assets/translation.sh b/.github/workflows/assets/translation.sh index 0fd7b8b..d017163 100644 --- a/.github/workflows/assets/translation.sh +++ b/.github/workflows/assets/translation.sh @@ -26,6 +26,7 @@ sync_repo_master() { update_local_merge_from_master() { local repo_dir="$1" lang_code="$2" local local_br="${LOCAL_BRANCH_PREFIX}${lang_code}" + set_git_bot_config "$repo_dir" git -C "$repo_dir" fetch origin "$MASTER_BRANCH" || return 2 git -C "$repo_dir" fetch origin "$local_br" || return 2 git -C "$repo_dir" checkout -B "$local_br" "origin/$local_br" || return 2 @@ -37,6 +38,7 @@ update_local_merge_from_master() { ensure_local_branch_in_repo() { local dest_repo="$1" sub_name="$2" lang_code="$3" local local_br="${LOCAL_BRANCH_PREFIX}${lang_code}" + set_git_bot_config "$dest_repo" if git -C "$dest_repo" ls-remote --exit-code --heads origin "$local_br" &>/dev/null; then echo " Branch $local_br already exists in $sub_name." >&2 return 0 @@ -57,7 +59,11 @@ process_local_branch() { if git -C "$dest_repo" ls-remote --exit-code --heads origin "$local_br" &>/dev/null; then has_open_translation_pr "$MODULE_ORG" "$sub_name" "$lang_code" case $? in - 0) echo " Open translation PR found for $sub_name ($local_br), skipping." >&2; return 1 ;; + 0) + OPEN_PR_SKIP+=("$sub_name") + echo " Open translation PR found for $sub_name ($local_br), skipping." >&2 + return 1 + ;; 2) return 2 ;; esac update_local_merge_from_master "$dest_repo" "$lang_code" || return 2 @@ -119,6 +125,7 @@ process_one_submodule() { clone_repo "$org_repo_url" "$MASTER_BRANCH" "$dest_repo" keep || { echo " clone_repo failed." >&2; return 2 } + set_git_bot_config "$dest_repo" fi local any_added=0 rc diff --git a/.github/workflows/start-translation.yml b/.github/workflows/start-translation.yml index 98d802f..cdc9523 100644 --- a/.github/workflows/start-translation.yml +++ b/.github/workflows/start-translation.yml @@ -10,7 +10,10 @@ # Jobs: # setup — validate lang_codes and emit matrix JSON. # sync-mirrors — sync mirror master for all submodules; update super-repo master. +# On partial submodule failure: finalize successful pointers, then exit non-zero. # start-local (matrix per lang) — local-{lang_code} in mirrors + super-repo + Weblate. +# Runs per language from setup when sync-mirrors produced successes (see job if:). +# Processes only submodules that succeeded in sync-mirrors. # # client_payload: # lang_codes: (optional) Comma-separated lang codes (e.g. zh_Hans,ja). Defaults to vars.LANG_CODES. @@ -64,6 +67,8 @@ jobs: sync-mirrors: needs: setup runs-on: ubuntu-latest + outputs: + updated_submodules: ${{ steps.sync.outputs.updated_submodules }} concurrency: group: translations-mirror-master cancel-in-progress: false @@ -82,6 +87,7 @@ jobs: git checkout "$MASTER_BRANCH" - name: Sync mirror master + id: sync shell: bash env: GITHUB_TOKEN: ${{ secrets.SYNC_TOKEN }} @@ -92,9 +98,6 @@ jobs: # shellcheck disable=SC2034 lang_codes_arr=() - META_MISSING=() - NO_DOC_PATHS=() - ORG_REPO_MISSING=() # shellcheck source=assets/env.sh source "$GITHUB_WORKSPACE/.github/workflows/assets/env.sh" @@ -104,6 +107,7 @@ jobs: source "$GITHUB_WORKSPACE/.github/workflows/assets/translation.sh" init_translation_state + init_submodule_summary_buckets begin_phase "$PHASE_SETUP" "Prepare workspace" validate_secrets @@ -138,6 +142,7 @@ jobs: [[ ${#submodule_names[@]} -eq 0 ]] && { echo "No libs/ submodules in .gitmodules, nothing to sync." >&2 + echo "updated_submodules=[]" >> "$GITHUB_OUTPUT" end_phase exit 0 } @@ -162,31 +167,39 @@ jobs: record_submodule_update "$sub" || true else rc=$? - [[ $rc -eq 2 ]] && submodule_fatal=$((submodule_fatal + 1)) + if [[ $rc -eq 2 ]]; then + record_submodule_fatal "$sub" + submodule_fatal=$((submodule_fatal + 1)) + fi fi done - echo "── Summary: Boost library documentation metadata ──" >&2 - echo " Type 1 — Missing or unreadable meta/libraries.json (${#META_MISSING[@]}): $([[ ${#META_MISSING[@]} -eq 0 ]] && echo '(none)' || echo "${META_MISSING[*]}")" >&2 - echo " Type 2 — No doc paths in meta/libraries.json (${#NO_DOC_PATHS[@]}): $([[ ${#NO_DOC_PATHS[@]} -eq 0 ]] && echo '(none)' || echo "${NO_DOC_PATHS[*]}")" >&2 - echo " Type 3 — Lib repo missing in org (run add-submodules first) (${#ORG_REPO_MISSING[@]}): $([[ ${#ORG_REPO_MISSING[@]} -eq 0 ]] && echo '(none)' || echo "${ORG_REPO_MISSING[*]}")" >&2 - - [[ $submodule_fatal -gt 0 ]] && { + # Buckets filled by process_one_submodule. + print_submodule_processing_summary + [[ $submodule_fatal -gt 0 ]] && \ phase_err "$submodule_fatal submodule(s) failed with errors." - end_phase - exit 1 - } end_phase begin_phase "$PHASE_FINALIZE_TRANSLATIONS" "Finalize translations master" rc=0 finalize_translations_master "$TRANS_DIR" "$LIBS_REF" || rc=$? end_phase - [[ $rc -ne 0 ]] && exit $rc + + exit_rc=0 + [[ $submodule_fatal -gt 0 ]] && exit_rc=1 + [[ $rc -ne 0 ]] && exit_rc=$rc + if [[ $exit_rc -eq 0 ]]; then + updated_json=$(submodule_names_to_json "${UPDATES[@]}") + echo "updated_submodules=$updated_json" >> "$GITHUB_OUTPUT" + echo "Submodules for start-local: $updated_json" >&2 + fi + [[ $exit_rc -ne 0 ]] && exit $exit_rc echo "Done." >&2 start-local: + # always() still evaluates outputs when sync-mirrors fails; updated_submodules is only set on full success. + if: ${{ always() && !cancelled() && needs.setup.result == 'success' && needs.sync-mirrors.outputs.updated_submodules != '' && needs.sync-mirrors.outputs.updated_submodules != '[]' }} needs: [setup, sync-mirrors] runs-on: ubuntu-latest strategy: @@ -220,13 +233,10 @@ jobs: WEBLATE_URL: ${{ secrets.WEBLATE_URL }} WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} SUBMODULES_ORG: ${{ vars.SUBMODULES_ORG }} + SYNC_MIRROR_UPDATES: ${{ needs.sync-mirrors.outputs.updated_submodules }} run: | set -euo pipefail - META_MISSING=() - NO_DOC_PATHS=() - ORG_REPO_MISSING=() - # shellcheck source=assets/env.sh source "$GITHUB_WORKSPACE/.github/workflows/assets/env.sh" # shellcheck source=assets/lib.sh @@ -235,6 +245,7 @@ jobs: source "$GITHUB_WORKSPACE/.github/workflows/assets/translation.sh" init_translation_state + init_submodule_summary_buckets begin_phase "$PHASE_SETUP" "Prepare workspace" validate_secrets weblate @@ -262,19 +273,11 @@ jobs: lang_codes_arr=("$LANG_CODE") init_add_or_update_lang "$LANG_CODE" - [[ ! -f .gitmodules ]] && { - phase_err ".gitmodules not found (run from translations repo with master checked out)" - end_phase - exit 1 - } - - mapfile -t submodule_names < <( - git config -f .gitmodules --get-regexp 'submodule\..*\.path' 2>/dev/null \ - | awk '{print $2}' | { grep '^libs/' || true; } | sed 's|^libs/||' - ) + echo "Submodules from sync-mirrors: ${SYNC_MIRROR_UPDATES:-[]}" >&2 + mapfile -t submodule_names < <(parse_submodule_names_json "${SYNC_MIRROR_UPDATES:-[]}") [[ ${#submodule_names[@]} -eq 0 ]] && { - echo "No libs/ submodules in .gitmodules, nothing to sync." >&2 + echo "No submodules succeeded in sync-mirrors, nothing to process." >&2 end_phase exit 0 } @@ -301,38 +304,43 @@ jobs: record_submodule_update "$sub" || true else rc=$? - [[ $rc -eq 2 ]] && submodule_fatal=$((submodule_fatal + 1)) + if [[ $rc -eq 2 ]]; then + record_submodule_fatal "$sub" + submodule_fatal=$((submodule_fatal + 1)) + fi fi done - echo "── Summary: Boost library documentation metadata ──" >&2 - echo " Type 1 — Missing or unreadable meta/libraries.json (${#META_MISSING[@]}): $([[ ${#META_MISSING[@]} -eq 0 ]] && echo '(none)' || echo "${META_MISSING[*]}")" >&2 - echo " Type 2 — No doc paths in meta/libraries.json (${#NO_DOC_PATHS[@]}): $([[ ${#NO_DOC_PATHS[@]} -eq 0 ]] && echo '(none)' || echo "${NO_DOC_PATHS[*]}")" >&2 - echo " Type 3 — Lib repo missing in org (run add-submodules first) (${#ORG_REPO_MISSING[@]}): $([[ ${#ORG_REPO_MISSING[@]} -eq 0 ]] && echo '(none)' || echo "${ORG_REPO_MISSING[*]}")" >&2 - - [[ $submodule_fatal -gt 0 ]] && { + # Buckets filled by process_one_submodule. + print_submodule_processing_summary + [[ $submodule_fatal -gt 0 ]] && \ phase_err "$submodule_fatal submodule(s) failed with errors." - end_phase - exit 1 - } end_phase begin_phase "$PHASE_FINALIZE_TRANSLATIONS" "Finalize translations local branch" rc=0 finalize_translations_local "$TRANS_DIR" "$LIBS_REF" "$LANG_CODE" || rc=$? end_phase - [[ $rc -ne 0 ]] && exit $rc - begin_phase "$PHASE_TRIGGER_WEBLATE" "Trigger Weblate add-or-update" - ext_arr=() - [[ -n "${EXTENSIONS:-}" ]] && mapfile -t ext_arr < <(parse_extensions "$EXTENSIONS") - exts_json="[]" - [[ ${#ext_arr[@]} -gt 0 ]] && \ - exts_json=$(printf '%s\n' "${ext_arr[@]}" | jq -R . | jq -s .) + weblate_rc=0 + if [[ $rc -eq 0 ]]; then + begin_phase "$PHASE_TRIGGER_WEBLATE" "Trigger Weblate add-or-update" + ext_arr=() + [[ -n "${EXTENSIONS:-}" ]] && mapfile -t ext_arr < <(parse_extensions "$EXTENSIONS") + exts_json="[]" + [[ ${#ext_arr[@]} -gt 0 ]] && \ + exts_json=$(printf '%s\n' "${ext_arr[@]}" | jq -R . | jq -s .) + + trigger_weblate "$WEBLATE_URL" "$WEBLATE_TOKEN" "$LIBS_REF" "$exts_json" "$LANG_CODE" || weblate_rc=$? + end_phase + else + echo "Skipping Weblate add-or-update: finalize_translations_local failed (rc=$rc)." >&2 + fi - rc=0 - trigger_weblate "$WEBLATE_URL" "$WEBLATE_TOKEN" "$LIBS_REF" "$exts_json" "$LANG_CODE" || rc=$? - end_phase - [[ $rc -ne 0 ]] && exit $rc + exit_rc=0 + [[ $submodule_fatal -gt 0 ]] && exit_rc=1 + [[ $rc -ne 0 ]] && exit_rc=$rc + [[ $weblate_rc -ne 0 ]] && exit_rc=$weblate_rc + [[ $exit_rc -ne 0 ]] && exit $exit_rc echo "Done." >&2 diff --git a/tests/helpers/common.bash b/tests/helpers/common.bash index b0b7269..e5ed2b7 100644 --- a/tests/helpers/common.bash +++ b/tests/helpers/common.bash @@ -44,6 +44,7 @@ reset_process_globals() { ORG_REPO_MISSING=() META_MISSING=() NO_DOC_PATHS=() + REPO_EXISTS_SKIP=() lang_codes_arr=() init_translation_state } diff --git a/tests/test_lib.bats b/tests/test_lib.bats index 8ac4751..888eac9 100644 --- a/tests/test_lib.bats +++ b/tests/test_lib.bats @@ -30,6 +30,27 @@ setup() { [ "$output" = "en" ] } +@test "submodule_names_to_json: empty array" { + run submodule_names_to_json + [ "$status" -eq 0 ] + [ "$output" = "[]" ] +} + +@test "submodule_names_to_json: encodes names" { + run submodule_names_to_json algorithm system + [ "$status" -eq 0 ] + [ "$output" = '["algorithm","system"]' ] +} + +@test "parse_submodule_names_json: empty and populated" { + run parse_submodule_names_json '[]' + [ "$status" -eq 0 ] + [ -z "$output" ] + run parse_submodule_names_json '["algorithm","system"]' + [ "$status" -eq 0 ] + [ "$output" = $'algorithm\nsystem' ] +} + @test "parse_extensions: bracketed extensions" { run parse_extensions "[.adoc, .md]" [ "$status" -eq 0 ] @@ -246,6 +267,34 @@ setup() { cleanup_git_fixture_root } +@test "finalize_translations_repo: stops on first failure" { + # shellcheck source=tests/helpers/git_fixtures.bash + source "$BATS_TEST_DIRNAME/helpers/git_fixtures.bash" + init_git_fixture_root + create_bare_remote_with_clone "translations" + trans_dir="$GIT_FIXTURE_ROOT/translations-work" + git clone "$BARE_REMOTE" "$trans_dir" + + UPDATES=("algorithm") + SYNC_CALLS=() + sync_translations_branch() { + if [[ "$2" == "${MASTER_BRANCH}" ]]; then + return 1 + fi + SYNC_CALLS+=("branch=$2 force=${4:-false}") + } + + if run_fn finalize_translations_repo "$trans_dir" "develop" "en" "zh_Hans"; then + rc=0 + else + rc=$? + fi + [ "$rc" -eq 1 ] + [ "${#SYNC_CALLS[@]}" -eq 0 ] + + cleanup_git_fixture_root +} + @test "begin_phase and end_phase: emit group markers and track CURRENT_PHASE" { local out_file="$BATS_TMPDIR/phase.out" @@ -361,3 +410,55 @@ setup() { [ "$status" -eq 1 ] [[ "$output" == *"invalid submodule name"* ]] } + +@test "record_submodule_fatal: deduplicates entries" { + init_translation_state + record_submodule_fatal "algorithm" + record_submodule_fatal "algorithm" + [ "${#SUBMODULE_FATAL[@]}" -eq 1 ] + [ "${SUBMODULE_FATAL[0]}" = "algorithm" ] +} + +@test "print_submodule_processing_summary: empty state shows (none) for all buckets" { + load_lib + reset_process_globals + run print_submodule_processing_summary + [ "$status" -eq 0 ] + [[ "$output" == *"Submodule processing summary"* ]] + [[ "$output" == *"Successfully updated (0): (none)"* ]] + [[ "$output" == *"Failed — Type 1"*"(none)"* ]] + [[ "$output" == *"Failed — processing error (0): (none)"* ]] + [[ "$output" == *"Skipped — open translation PR (0): (none)"* ]] +} + +@test "print_submodule_processing_summary: lists populated buckets" { + load_lib + reset_process_globals + UPDATES=("algorithm") + META_MISSING=("broken_meta") + NO_DOC_PATHS=("no_docs") + ORG_REPO_MISSING=("missing_repo") + REPO_EXISTS_SKIP=("existing_repo") + OPEN_PR_SKIP=("open_pr_lib") + SUBMODULE_FATAL=("broken_meta" "clone_failed") + run print_submodule_processing_summary + [ "$status" -eq 0 ] + [[ "$output" == *"Successfully updated (1): algorithm"* ]] + [[ "$output" == *"Failed — Type 1"*broken_meta* ]] + [[ "$output" == *"Failed — Type 3"*missing_repo* ]] + [[ "$output" == *"Failed — processing error (1): clone_failed"* ]] + [[ "$output" == *"Skipped — Type 2"*no_docs* ]] + [[ "$output" == *"Skipped — org repo already exists"*existing_repo* ]] + [[ "$output" == *"Skipped — open translation PR"*open_pr_lib* ]] +} + +@test "print_submodule_processing_summary: does not double-list categorized fatal errors" { + load_lib + reset_process_globals + META_MISSING=("broken_meta") + SUBMODULE_FATAL=("broken_meta" "sync_failed") + run print_submodule_processing_summary + [ "$status" -eq 0 ] + [[ "$output" == *"Failed — processing error (1): sync_failed"* ]] + [[ "$output" != *"processing error (2)"* ]] +}