diff --git a/tests/README.md b/tests/README.md index f87c2e8..d62f067 100644 --- a/tests/README.md +++ b/tests/README.md @@ -22,8 +22,12 @@ The current salvage tests cover: - Samba-compatible history naming with `Copy #1 of NAME`, - report-file generation with `--out FILE`. -The NCP smoke scripts are intended to run as the same Unix user that normally -runs the test client, not necessarily as root. Pre-clean of old `.recycle` or +`tests/salvage/salvage_smoke_suite.sh` is the single NCP integration entry +point. It creates and deletes the same file twice, so one report covers both +the basic delete capture and the history/version case. + +The NCP smoke suite is intended to run as the same Unix user that normally runs +the test client, not necessarily as root. Pre-clean of old `.recycle` or `.salvage` artifacts is therefore best-effort: permission failures are reported as warnings and do not by themselves fail the smoke. The actual pass/fail check is based on artifacts created by the NCP delete path. diff --git a/tests/salvage/CMakeLists.txt b/tests/salvage/CMakeLists.txt index 6c3117c..467e57d 100644 --- a/tests/salvage/CMakeLists.txt +++ b/tests/salvage/CMakeLists.txt @@ -27,43 +27,27 @@ set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${SALVAGE_LAYOUT_SMOKE_SCRIPT} ) -set(SALVAGE_NCP_DELETE_SMOKE_SCRIPT - ${CMAKE_CURRENT_BINARY_DIR}/salvage_ncp_delete_smoke.sh +set(SALVAGE_SMOKE_SUITE_SCRIPT + ${CMAKE_CURRENT_BINARY_DIR}/salvage_smoke_suite.sh ) add_custom_command( - OUTPUT ${SALVAGE_NCP_DELETE_SMOKE_SCRIPT} + OUTPUT ${SALVAGE_SMOKE_SUITE_SCRIPT} COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_CURRENT_SOURCE_DIR}/salvage_ncp_delete_smoke.sh - ${SALVAGE_NCP_DELETE_SMOKE_SCRIPT} - COMMAND ${CMAKE_COMMAND} -E env chmod +x ${SALVAGE_NCP_DELETE_SMOKE_SCRIPT} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/salvage_ncp_delete_smoke.sh - COMMENT "Copying salvage NCP delete smoke helper" + ${CMAKE_CURRENT_SOURCE_DIR}/salvage_smoke_suite.sh + ${SALVAGE_SMOKE_SUITE_SCRIPT} + COMMAND ${CMAKE_COMMAND} -E env chmod +x ${SALVAGE_SMOKE_SUITE_SCRIPT} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/salvage_smoke_suite.sh + COMMENT "Copying salvage NCP smoke suite" VERBATIM ) -add_custom_target(salvage_ncp_delete_smoke_suite ALL - DEPENDS ${SALVAGE_NCP_DELETE_SMOKE_SCRIPT} +add_custom_target(salvage_smoke_suite ALL + DEPENDS ${SALVAGE_SMOKE_SUITE_SCRIPT} ) - -set(SALVAGE_NCP_HISTORY_SMOKE_SCRIPT - ${CMAKE_CURRENT_BINARY_DIR}/salvage_ncp_history_smoke.sh -) - -add_custom_command( - OUTPUT ${SALVAGE_NCP_HISTORY_SMOKE_SCRIPT} - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_CURRENT_SOURCE_DIR}/salvage_ncp_history_smoke.sh - ${SALVAGE_NCP_HISTORY_SMOKE_SCRIPT} - COMMAND ${CMAKE_COMMAND} -E env chmod +x ${SALVAGE_NCP_HISTORY_SMOKE_SCRIPT} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/salvage_ncp_history_smoke.sh - COMMENT "Copying salvage NCP history smoke helper" - VERBATIM -) - -add_custom_target(salvage_ncp_history_smoke_suite ALL - DEPENDS ${SALVAGE_NCP_HISTORY_SMOKE_SCRIPT} +set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES + ${SALVAGE_SMOKE_SUITE_SCRIPT} ) find_path(SALVAGE_NCPFS_INCLUDE_DIR diff --git a/tests/salvage/README.md b/tests/salvage/README.md index 0f16c13..4c0065d 100644 --- a/tests/salvage/README.md +++ b/tests/salvage/README.md @@ -100,19 +100,27 @@ require a running mars_nwe server and does not use NCP. It verifies the basic `.recycle`/`.salvage` directory contract and stale JSON detection. -## NCP delete capture smoke +## NCP smoke suite -`salvage_ncp_delete_smoke.sh` is the integration check for the server-side -delete hook. It uses `ncp_delete_smoke`, a small libncp client, to create and -delete a file through classic NetWare NCP file functions. The script does not -call local `rm`/`unlink` for the tested live path; local filesystem access is -used only after the NCP delete to inspect the expected recycle payload and JSON -sidecar. +`salvage_smoke_suite.sh` is the single integration check for the server-side +delete hook and Samba-compatible history behaviour. It uses `ncp_delete_smoke`, +a small libncp client, to create and delete the same NetWare path twice through +classic NetWare NCP file functions. + +The suite covers both formerly separate checks: + +- first delete: verifies normal `.recycle` payload and `.salvage` JSON capture, +- second delete: verifies version naming with `Copy #1 of NAME` when option `53` + contains the `v` flag. + +The script does not call local `rm`/`unlink` for the tested live path; local +filesystem access is used only after the NCP delete to inspect the expected +recycle payloads and JSON sidecars. Example: ```sh -./tests/salvage/salvage_ncp_delete_smoke.sh \ +./tests/salvage/salvage_smoke_suite.sh \ -S MARS -U SUPERVISOR -P secret \ --path SYS:PUBLIC/SLVGCHK.TXT \ --unix-path /var/mars_nwe/SYS/public/SLVGCHK.TXT \ @@ -122,30 +130,14 @@ Example: The script can run as a normal user. Best-effort pre-clean of stale test artifacts may warn when existing `.recycle`/`.salvage` files are owned by the -server user or root; those warnings do not fail the smoke by themselves. +server user or root; those warnings do not fail the smoke by themselves. For +cleanest results, use a fresh test filename or run cleanup with the same Unix +account that owns the mars_nwe volume files. -## NCP history/version smoke - -`salvage_ncp_history_smoke.sh` deletes the same NetWare path twice through NCP -and verifies Samba-compatible version naming. With the `v` flag enabled, the -first recycled payload keeps its original name and the second is written as -`Copy #1 of NAME` in the same recycle directory. The `.salvage` sidecar uses -the same selected payload name with a `.json` suffix. - -Example: - -```sh -./tests/salvage/salvage_ncp_history_smoke.sh \ - -S MARS -U SUPERVISOR -P secret \ - --path SYS:PUBLIC/SLVGHIST.TXT \ - --unix-path /var/mars_nwe/SYS/public/SLVGHIST.TXT \ - --volume-root /var/mars_nwe/SYS \ - --out /tmp/mars-salvage-history-report.txt -``` - -Like the delete smoke, stale artifact cleanup is best-effort and warning-only. -This matters when the script is run as a normal user while `.recycle` and -`.salvage` entries are owned by the mars_nwe server account. +The old split scripts `salvage_ncp_delete_smoke.sh` and +`salvage_ncp_history_smoke.sh` were replaced by this single suite so the salvage +flow is exercised like the AFP smoke suite: one entry point, one report, and one +summary. ## Still to implement diff --git a/tests/salvage/salvage_ncp_delete_smoke.sh b/tests/salvage/salvage_ncp_delete_smoke.sh deleted file mode 100755 index 7e41fe7..0000000 --- a/tests/salvage/salvage_ncp_delete_smoke.sh +++ /dev/null @@ -1,366 +0,0 @@ -#!/usr/bin/env bash -# Verify that a file deleted through mars_nwe's normal NCP delete path is -# captured into .recycle with a .salvage JSON sidecar. - -set -u - -SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -SERVER="MARS" -USER_NAME="SUPERVISOR" -PASSWORD="" -NETWARE_PATH="SYS:PUBLIC/SLVGCHK.TXT" -UNIX_PATH="/var/mars_nwe/SYS/public/SLVGCHK.TXT" -VOLUME_ROOT="" -RECYCLE_REPOSITORY=".recycle" -SALVAGE_REPOSITORY=".salvage" -DELETED_BY="" -OUT_FILE="" -KEEP_GOING=1 -CLEAN_ARTIFACTS=1 - -usage() { - cat <&2 - usage >&2 - exit 2 ;; - esac -done - -if [ -z "$PASSWORD" ]; then - echo "Missing password. Use -P PASSWORD." >&2 - usage >&2 - exit 2 -fi -if [ -z "$DELETED_BY" ]; then - DELETED_BY=$USER_NAME -fi -case "$NETWARE_PATH" in - *:*) ;; - *) echo "--path must include a NetWare volume prefix, e.g. SYS:PUBLIC/FILE" >&2; exit 2 ;; -esac -case "$UNIX_PATH" in - /*) ;; - *) echo "--unix-path must be absolute" >&2; exit 2 ;; -esac -if [ -n "$VOLUME_ROOT" ]; then - case "$VOLUME_ROOT" in - /*) ;; - *) echo "--volume-root must be absolute" >&2; exit 2 ;; - esac -fi - -REPORT_TMP=$(mktemp "${TMPDIR:-/tmp}/mars-salvage-ncp.XXXXXX") -FAILURES=0 - -cleanup() { - rm -f "$REPORT_TMP" -} -trap cleanup EXIT INT TERM - -emit() { - printf '%s\n' "$*" | tee -a "$REPORT_TMP" -} - -section() { - emit "" - emit "## $*" -} - -finish_report() { - section "Summary" - emit "failures=$FAILURES" - if [ -n "$OUT_FILE" ]; then - cp "$REPORT_TMP" "$OUT_FILE" - emit "report=$OUT_FILE" - fi -} - -fail_check() { - emit "$*" - FAILURES=$((FAILURES + 1)) - if [ "$KEEP_GOING" -eq 0 ]; then - finish_report - exit 1 - fi -} - -run_cmd() { - local label=$1 - local printable=$2 - shift 2 - - section "$label" - emit "\$ $printable" - "$@" 2>&1 | tee -a "$REPORT_TMP" - local status=${PIPESTATUS[0]} - emit "[exit=$status]" - if [ "$status" -ne 0 ]; then - fail_check "$label failed" - fi -} - -count_path_components() { - local path=$1 - local old_ifs=$IFS - local count=0 - local part - - path=${path#*:} - path=${path#/} - path=${path#\\} - path=${path//\\//} - IFS=/ - for part in $path; do - [ -n "$part" ] && count=$((count + 1)) - done - IFS=$old_ifs - printf '%s\n' "$count" -} - -compute_unix_volume_root() { - local root - local components - - root=$(dirname -- "$UNIX_PATH") - components=$(count_path_components "$NETWARE_PATH") || return 1 - while [ "$components" -gt 1 ]; do - root=$(dirname -- "$root") - components=$((components - 1)) - done - printf '%s\n' "$root" -} - -relative_to_volume_root() { - local path=$1 - local root=$2 - - case "$path" in - "$root"/*) printf '%s\n' "${path#$root/}" ;; - *) return 1 ;; - esac -} - -relative_from_netware_path() { - local path=$1 - - path=${path#*:} - path=${path#/} - path=${path#\\} - path=${path//\\//} - printf '%s\n' "$path" -} - -resolve_case_path() { - local root=$1 - local rel=$2 - local current=$root - local old_ifs=$IFS - local part entry found - - rel=${rel#/} - IFS=/ - for part in $rel; do - [ -z "$part" ] && continue - found="" - if [ -d "$current" ]; then - for entry in "$current"/* "$current"/.[!.]* "$current"/..?*; do - [ -e "$entry" ] || continue - if [ "$(basename -- "$entry" | tr '[:upper:]' '[:lower:]')" = \ - "$(printf '%s' "$part" | tr '[:upper:]' '[:lower:]')" ]; then - found=$entry - break - fi - done - fi - if [ -n "$found" ]; then - current=$found - else - current=$current/$part - fi - done - IFS=$old_ifs - printf '%s\n' "$current" -} - - -prepare_expected_paths() { - if [ -z "$VOLUME_ROOT" ]; then - VOLUME_ROOT=$(compute_unix_volume_root) || return 1 - fi - if [ -z "$RELATIVE_PATH" ]; then - RELATIVE_PATH=$(relative_to_volume_root "$UNIX_PATH" "$VOLUME_ROOT") || \ - RELATIVE_PATH=$(relative_from_netware_path "$NETWARE_PATH") || return 1 - fi - RECYCLE_PATH=$(resolve_case_path "$VOLUME_ROOT" "$RECYCLE_REPOSITORY/$DELETED_BY/$RELATIVE_PATH") - METADATA_PATH=$(resolve_case_path "$VOLUME_ROOT" "$SALVAGE_REPOSITORY/$DELETED_BY/$RELATIVE_PATH.json") - return 0 -} - -clean_stale_artifacts() { - if [ "$CLEAN_ARTIFACTS" -eq 0 ]; then - return 0 - fi - section "pre-clean stale salvage artifacts" - if [ -n "$METADATA_PATH" ] && [ -e "$METADATA_PATH" ]; then - emit "rm '$METADATA_PATH'" - if ! rm -f -- "$METADATA_PATH"; then - emit "warning: could not remove stale metadata: $METADATA_PATH" - fi - else - emit "metadata clean" - fi - if [ -n "$RECYCLE_PATH" ] && [ -e "$RECYCLE_PATH" ]; then - emit "rm '$RECYCLE_PATH'" - if ! rm -f -- "$RECYCLE_PATH"; then - emit "warning: could not remove stale recycle payload: $RECYCLE_PATH" - fi - else - emit "recycle payload clean" - fi -} - -check_file() { - local label=$1 - local path=$2 - - section "$label" - emit "path=$path" - if [ ! -f "$path" ]; then - fail_check "missing file: $path" - return 1 - fi - return 0 -} - -run_ncp_create_delete() { - local status - - section "NCP create/delete" - emit "\$ ./ncp_delete_smoke -S '$SERVER' -U '$USER_NAME' -P ****** '$NETWARE_PATH'" - "$SCRIPT_DIR/ncp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$NETWARE_PATH" 2>&1 | tee -a "$REPORT_TMP" - status=${PIPESTATUS[0]} - emit "[exit=$status]" - return "$status" -} - -check_metadata() { - local path=$1 - - section "salvage metadata JSON" - emit "\$ cat '$path'" - cat "$path" 2>&1 | tee -a "$REPORT_TMP" - local status=${PIPESTATUS[0]} - emit "[exit=$status]" - if [ "$status" -ne 0 ]; then - fail_check "could not cat metadata: $path" - return 1 - fi - - grep -q '"source"[[:space:]]*:[[:space:]]*"mars_nwe"' "$path" || fail_check "metadata missing source=mars_nwe" - grep -q '"original_path"' "$path" || fail_check "metadata missing original_path" - grep -q '"recycle_relative_path"' "$path" || fail_check "metadata missing recycle_relative_path" - grep -q '"salvage_relative_path"' "$path" || fail_check "metadata missing salvage_relative_path" - grep -q '"trustees"' "$path" || fail_check "metadata missing trustees" -} - -section "mars_nwe salvage NCP delete smoke" -emit "date=$(date -Is)" -emit "server=$SERVER" -emit "user=$USER_NAME" -emit "path=$NETWARE_PATH" -emit "unix_path=$UNIX_PATH" -if [ -n "$VOLUME_ROOT" ]; then - emit "volume_root_arg=$VOLUME_ROOT" -fi -emit "deleted_by=$DELETED_BY" -emit "recycle_repository=$RECYCLE_REPOSITORY" -emit "salvage_repository=$SALVAGE_REPOSITORY" -emit "clean_artifacts=$CLEAN_ARTIFACTS" - -RELATIVE_PATH="" -RECYCLE_PATH="" -METADATA_PATH="" -if prepare_expected_paths; then - emit "volume_root=$VOLUME_ROOT" - emit "relative_path=$RELATIVE_PATH" - emit "recycle_path=$RECYCLE_PATH" - emit "metadata_path=$METADATA_PATH" - clean_stale_artifacts -else - fail_check "could not prepare expected paths before NCP delete" -fi - -run_ncp_create_delete -NCP_STATUS=$? - -ARTIFACT_FAILURES=$FAILURES -if [ -n "$RELATIVE_PATH" ]; then - check_file "recycle payload" "$RECYCLE_PATH" - check_file "salvage metadata sidecar" "$METADATA_PATH" - if [ -f "$METADATA_PATH" ]; then - check_metadata "$METADATA_PATH" - fi -fi -ARTIFACT_FAILURES=$((FAILURES - ARTIFACT_FAILURES)) - -if [ "$NCP_STATUS" -ne 0 ]; then - if [ "$ARTIFACT_FAILURES" -eq 0 ]; then - section "NCP status" - emit "warning: NCP helper exited $NCP_STATUS after salvage artifacts were created" - else - fail_check "NCP create/delete failed" - fi -fi - -finish_report -[ "$FAILURES" -eq 0 ] diff --git a/tests/salvage/salvage_ncp_history_smoke.sh b/tests/salvage/salvage_ncp_history_smoke.sh deleted file mode 100755 index b2feb5d..0000000 --- a/tests/salvage/salvage_ncp_history_smoke.sh +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env bash -# Verify Samba-compatible salvage versioning through the normal NCP delete path. - -set -u - -SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -SERVER="MARS" -USER_NAME="SUPERVISOR" -PASSWORD="" -NETWARE_PATH="SYS:PUBLIC/SLVGHIST.TXT" -UNIX_PATH="/var/mars_nwe/SYS/public/SLVGHIST.TXT" -VOLUME_ROOT="" -RECYCLE_REPOSITORY=".recycle" -SALVAGE_REPOSITORY=".salvage" -DELETED_BY="" -OUT_FILE="" -FAILURES=0 - -usage() { - cat <&2; usage >&2; exit 2 ;; - esac -done - -[ -n "$PASSWORD" ] || { echo "Missing password. Use -P PASSWORD." >&2; exit 2; } -[ -n "$DELETED_BY" ] || DELETED_BY=$USER_NAME -case "$UNIX_PATH" in /*) ;; *) echo "--unix-path must be absolute" >&2; exit 2 ;; esac -case "$NETWARE_PATH" in *:*) ;; *) echo "--path must include a volume prefix" >&2; exit 2 ;; esac - -REPORT_TMP=$(mktemp "${TMPDIR:-/tmp}/mars-salvage-history.XXXXXX") -trap 'rm -f "$REPORT_TMP"' EXIT INT TERM - -emit() { printf '%s\n' "$*" | tee -a "$REPORT_TMP"; } -section() { emit ""; emit "## $*"; } -fail_check() { emit "$*"; FAILURES=$((FAILURES + 1)); } -finish_report() { - section "Summary" - emit "failures=$FAILURES" - if [ -n "$OUT_FILE" ]; then - cp "$REPORT_TMP" "$OUT_FILE" - emit "report=$OUT_FILE" - fi -} - -count_path_components() { - local path=$1 old_ifs=$IFS count=0 part - path=${path#*:}; path=${path#/}; path=${path#\\}; path=${path//\\//} - IFS=/ - for part in $path; do [ -n "$part" ] && count=$((count + 1)); done - IFS=$old_ifs - printf '%s\n' "$count" -} - -compute_unix_volume_root() { - local root components - root=$(dirname -- "$UNIX_PATH") - components=$(count_path_components "$NETWARE_PATH") || return 1 - while [ "$components" -gt 1 ]; do root=$(dirname -- "$root"); components=$((components - 1)); done - printf '%s\n' "$root" -} - -relative_to_volume_root() { - local path=$1 root=$2 - case "$path" in "$root"/*) printf '%s\n' "${path#$root/}" ;; *) return 1 ;; esac -} - -run_delete() { - local label=$1 status - section "$label" - emit "\$ ./ncp_delete_smoke -S '$SERVER' -U '$USER_NAME' -P ****** '$NETWARE_PATH'" - "$SCRIPT_DIR/ncp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$NETWARE_PATH" 2>&1 | tee -a "$REPORT_TMP" - status=${PIPESTATUS[0]} - emit "[exit=$status]" - if [ "$status" -ne 0 ]; then - emit "warning: NCP helper exited $status; validating salvage artifacts anyway" - fi -} - -check_file() { - local label=$1 path=$2 - section "$label" - emit "path=$path" - [ -f "$path" ] || { fail_check "missing file: $path"; return 1; } - return 0 -} - -cat_metadata() { - local path=$1 - section "metadata JSON: $path" - emit "\$ cat '$path'" - cat "$path" 2>&1 | tee -a "$REPORT_TMP" - local status=${PIPESTATUS[0]} - emit "[exit=$status]" - [ "$status" -eq 0 ] || fail_check "could not cat metadata: $path" -} - -section "mars_nwe salvage NCP history smoke" -emit "date=$(date -Is)" -emit "server=$SERVER" -emit "user=$USER_NAME" -emit "path=$NETWARE_PATH" -emit "unix_path=$UNIX_PATH" -emit "deleted_by=$DELETED_BY" - -if [ -z "$VOLUME_ROOT" ]; then - VOLUME_ROOT=$(compute_unix_volume_root) || { fail_check "could not compute volume root"; VOLUME_ROOT=""; } -fi -RELATIVE_PATH="" -if [ -n "$VOLUME_ROOT" ]; then - RELATIVE_PATH=$(relative_to_volume_root "$UNIX_PATH" "$VOLUME_ROOT") || fail_check "could not derive relative path" -fi - -BASENAME=$(basename -- "$RELATIVE_PATH") -DIRNAME=$(dirname -- "$RELATIVE_PATH") -[ "$DIRNAME" = "." ] && DIRNAME="" -COPY_NAME="Copy #1 of $BASENAME" -if [ -n "$DIRNAME" ]; then - FIRST_REL="$DELETED_BY/$DIRNAME/$BASENAME" - SECOND_REL="$DELETED_BY/$DIRNAME/$COPY_NAME" -else - FIRST_REL="$DELETED_BY/$BASENAME" - SECOND_REL="$DELETED_BY/$COPY_NAME" -fi - -FIRST_RECYCLE="$VOLUME_ROOT/$RECYCLE_REPOSITORY/$FIRST_REL" -SECOND_RECYCLE="$VOLUME_ROOT/$RECYCLE_REPOSITORY/$SECOND_REL" -FIRST_META="$VOLUME_ROOT/$SALVAGE_REPOSITORY/$FIRST_REL.json" -SECOND_META="$VOLUME_ROOT/$SALVAGE_REPOSITORY/$SECOND_REL.json" - -emit "volume_root=$VOLUME_ROOT" -emit "relative_path=$RELATIVE_PATH" -emit "first_recycle=$FIRST_RECYCLE" -emit "second_recycle=$SECOND_RECYCLE" -emit "first_metadata=$FIRST_META" -emit "second_metadata=$SECOND_META" - -section "pre-clean test history artifacts" -for artifact in "$FIRST_META" "$SECOND_META" "$FIRST_RECYCLE" "$SECOND_RECYCLE"; do - if [ -e "$artifact" ]; then - emit "rm '$artifact'" - if rm -f -- "$artifact" 2>>"$REPORT_TMP"; then - : - else - emit "warning: could not remove stale test artifact: $artifact" - fi - fi -done - -run_delete "NCP create/delete #1" -run_delete "NCP create/delete #2" - -check_file "first recycle payload" "$FIRST_RECYCLE" -check_file "second recycle payload" "$SECOND_RECYCLE" -check_file "first salvage metadata" "$FIRST_META" && cat_metadata "$FIRST_META" -check_file "second salvage metadata" "$SECOND_META" && cat_metadata "$SECOND_META" - -if [ -f "$SECOND_META" ]; then - grep -q "Copy #1 of $BASENAME" "$SECOND_META" || fail_check "second metadata missing Copy #1 path" -fi - -finish_report -[ "$FAILURES" -eq 0 ] diff --git a/tests/salvage/salvage_smoke_suite.sh b/tests/salvage/salvage_smoke_suite.sh new file mode 100755 index 0000000..ff1d694 --- /dev/null +++ b/tests/salvage/salvage_smoke_suite.sh @@ -0,0 +1,286 @@ +#!/usr/bin/env bash +# End-to-end mars_nwe salvage smoke suite. +# +# The suite creates and deletes one NetWare file through NCP twice. The first +# delete verifies the normal .recycle/.salvage capture path; the second delete +# verifies Samba-compatible versioning (Copy #1 of NAME) when option 53 contains +# the 'v' flag. + +set -u + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +SERVER="MARS" +USER_NAME="SUPERVISOR" +PASSWORD="" +PATH_SET=0 +UNIX_PATH_SET=0 +NETWARE_PATH="SYS:PUBLIC/SLVGCHK.TXT" +UNIX_PATH="/var/mars_nwe/SYS/public/SLVGCHK.TXT" +VOLUME_ROOT="" +RECYCLE_REPOSITORY=".recycle" +SALVAGE_REPOSITORY=".salvage" +DELETED_BY="" +OUT_FILE="" +KEEP_GOING=1 +CLEAN_ARTIFACTS=1 + +usage() { + cat <&2; usage >&2; exit 2 ;; + esac +done + +[ -n "$PASSWORD" ] || { echo "Missing password. Use -P PASSWORD." >&2; usage >&2; exit 2; } +[ -n "$DELETED_BY" ] || DELETED_BY=$USER_NAME +case "$NETWARE_PATH" in *:*) ;; *) echo "--path must include a NetWare volume prefix" >&2; exit 2 ;; esac +case "$UNIX_PATH" in /*) ;; *) echo "--unix-path must be absolute" >&2; exit 2 ;; esac +if [ -n "$VOLUME_ROOT" ]; then + case "$VOLUME_ROOT" in /*) ;; *) echo "--volume-root must be absolute" >&2; exit 2 ;; esac +fi +if [ "$PATH_SET" -ne "$UNIX_PATH_SET" ]; then + echo "--path and --unix-path should be supplied together so both point to the same file" >&2 + exit 2 +fi + +REPORT_TMP=$(mktemp "${TMPDIR:-/tmp}/mars-salvage-suite.XXXXXX") +FAILURES=0 +NCP_FAILURES=0 +trap 'rm -f "$REPORT_TMP"' EXIT INT TERM + +emit() { printf '%s\n' "$*" | tee -a "$REPORT_TMP"; } +section() { emit ""; emit "## $*"; } +finish_report() { + section "Summary" + emit "failures=$FAILURES" + emit "ncp_warnings=$NCP_FAILURES" + if [ -n "$OUT_FILE" ]; then + cp "$REPORT_TMP" "$OUT_FILE" + emit "report=$OUT_FILE" + fi +} +fail_check() { + emit "$*" + FAILURES=$((FAILURES + 1)) + if [ "$KEEP_GOING" -eq 0 ]; then + finish_report + exit 1 + fi +} + +count_path_components() { + local path=$1 old_ifs=$IFS count=0 part + path=${path#*:}; path=${path#/}; path=${path#\\}; path=${path//\\//} + IFS=/ + for part in $path; do [ -n "$part" ] && count=$((count + 1)); done + IFS=$old_ifs + printf '%s\n' "$count" +} + +compute_unix_volume_root() { + local root components + root=$(dirname -- "$UNIX_PATH") + components=$(count_path_components "$NETWARE_PATH") || return 1 + while [ "$components" -gt 1 ]; do root=$(dirname -- "$root"); components=$((components - 1)); done + printf '%s\n' "$root" +} + +relative_to_volume_root() { + local path=$1 root=$2 + case "$path" in "$root"/*) printf '%s\n' "${path#$root/}" ;; *) return 1 ;; esac +} + +relative_from_netware_path() { + local path=$1 + path=${path#*:}; path=${path#/}; path=${path#\\}; path=${path//\\//} + printf '%s\n' "$path" +} + +resolve_case_path() { + local root=$1 rel=$2 current=$root old_ifs=$IFS part entry found + rel=${rel#/} + IFS=/ + for part in $rel; do + [ -z "$part" ] && continue + found="" + if [ -d "$current" ]; then + for entry in "$current"/* "$current"/.[!.]* "$current"/..?*; do + [ -e "$entry" ] || continue + if [ "$(basename -- "$entry" | tr '[:upper:]' '[:lower:]')" = \ + "$(printf '%s' "$part" | tr '[:upper:]' '[:lower:]')" ]; then + found=$entry + break + fi + done + fi + if [ -n "$found" ]; then current=$found; else current=$current/$part; fi + done + IFS=$old_ifs + printf '%s\n' "$current" +} + +prepare_paths() { + if [ -z "$VOLUME_ROOT" ]; then + VOLUME_ROOT=$(compute_unix_volume_root) || return 1 + fi + RELATIVE_PATH=$(relative_to_volume_root "$UNIX_PATH" "$VOLUME_ROOT") || \ + RELATIVE_PATH=$(relative_from_netware_path "$NETWARE_PATH") || return 1 + + BASENAME=$(basename -- "$RELATIVE_PATH") + DIRNAME=$(dirname -- "$RELATIVE_PATH") + [ "$DIRNAME" = "." ] && DIRNAME="" + COPY_NAME="Copy #1 of $BASENAME" + + if [ -n "$DIRNAME" ]; then + FIRST_REL="$DELETED_BY/$DIRNAME/$BASENAME" + SECOND_REL="$DELETED_BY/$DIRNAME/$COPY_NAME" + else + FIRST_REL="$DELETED_BY/$BASENAME" + SECOND_REL="$DELETED_BY/$COPY_NAME" + fi + + FIRST_RECYCLE=$(resolve_case_path "$VOLUME_ROOT" "$RECYCLE_REPOSITORY/$FIRST_REL") + SECOND_RECYCLE=$(resolve_case_path "$VOLUME_ROOT" "$RECYCLE_REPOSITORY/$SECOND_REL") + FIRST_META=$(resolve_case_path "$VOLUME_ROOT" "$SALVAGE_REPOSITORY/$FIRST_REL.json") + SECOND_META=$(resolve_case_path "$VOLUME_ROOT" "$SALVAGE_REPOSITORY/$SECOND_REL.json") +} + +clean_artifact() { + local artifact=$1 + if [ -e "$artifact" ]; then + emit "rm '$artifact'" + if rm -f -- "$artifact" 2>>"$REPORT_TMP"; then + : + else + emit "warning: could not remove stale test artifact: $artifact" + fi + else + emit "clean: $artifact" + fi +} + +clean_stale_artifacts() { + [ "$CLEAN_ARTIFACTS" -eq 1 ] || return 0 + section "pre-clean stale salvage artifacts" + clean_artifact "$FIRST_META" + clean_artifact "$SECOND_META" + clean_artifact "$FIRST_RECYCLE" + clean_artifact "$SECOND_RECYCLE" +} + +run_ncp_create_delete() { + local label=$1 status before_failures artifacts_delta + section "$label" + emit "\$ ./ncp_delete_smoke -S '$SERVER' -U '$USER_NAME' -P ****** '$NETWARE_PATH'" + "$SCRIPT_DIR/ncp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$NETWARE_PATH" 2>&1 | tee -a "$REPORT_TMP" + status=${PIPESTATUS[0]} + emit "[exit=$status]" + if [ "$status" -ne 0 ]; then + NCP_FAILURES=$((NCP_FAILURES + 1)) + emit "warning: NCP helper exited $status; validating salvage artifacts anyway" + fi +} + +check_file() { + local label=$1 path=$2 + section "$label" + emit "path=$path" + [ -f "$path" ] || { fail_check "missing file: $path"; return 1; } + return 0 +} + +cat_metadata() { + local label=$1 path=$2 + section "$label" + emit "\$ cat '$path'" + cat "$path" 2>&1 | tee -a "$REPORT_TMP" + local status=${PIPESTATUS[0]} + emit "[exit=$status]" + [ "$status" -eq 0 ] || { fail_check "could not cat metadata: $path"; return 1; } + grep -q '"source"[[:space:]]*:[[:space:]]*"mars_nwe"' "$path" || fail_check "metadata missing source=mars_nwe" + grep -q '"original_path"' "$path" || fail_check "metadata missing original_path" + grep -q '"recycle_relative_path"' "$path" || fail_check "metadata missing recycle_relative_path" + grep -q '"salvage_relative_path"' "$path" || fail_check "metadata missing salvage_relative_path" + grep -q '"trustees"' "$path" || fail_check "metadata missing trustees" +} + +section "mars_nwe salvage smoke suite" +emit "date=$(date -Is)" +emit "server=$SERVER" +emit "user=$USER_NAME" +emit "path=$NETWARE_PATH" +emit "unix_path=$UNIX_PATH" +if [ -n "$VOLUME_ROOT" ]; then emit "volume_root_arg=$VOLUME_ROOT"; fi +emit "deleted_by=$DELETED_BY" +emit "recycle_repository=$RECYCLE_REPOSITORY" +emit "salvage_repository=$SALVAGE_REPOSITORY" +emit "clean_artifacts=$CLEAN_ARTIFACTS" + +if prepare_paths; then + emit "volume_root=$VOLUME_ROOT" + emit "relative_path=$RELATIVE_PATH" + emit "first_recycle=$FIRST_RECYCLE" + emit "second_recycle=$SECOND_RECYCLE" + emit "first_metadata=$FIRST_META" + emit "second_metadata=$SECOND_META" + clean_stale_artifacts +else + fail_check "could not prepare expected salvage paths" +fi + +run_ncp_create_delete "NCP create/delete #1" +check_file "delete smoke: recycle payload" "$FIRST_RECYCLE" +check_file "delete smoke: salvage metadata sidecar" "$FIRST_META" && \ + cat_metadata "delete smoke: metadata JSON" "$FIRST_META" + +run_ncp_create_delete "NCP create/delete #2" +check_file "history smoke: second recycle payload" "$SECOND_RECYCLE" +check_file "history smoke: second salvage metadata sidecar" "$SECOND_META" && \ + cat_metadata "history smoke: metadata JSON" "$SECOND_META" + +if [ -f "$SECOND_META" ]; then + grep -q "Copy #1 of $BASENAME" "$SECOND_META" || fail_check "second metadata missing Copy #1 path" +fi + +finish_report +[ "$FAILURES" -eq 0 ]