tests: consolidate salvage NCP smoke scripts

This commit is contained in:
OpenAI
2026-05-31 14:23:10 +00:00
committed by Mario Fetka
parent 3466ae05b4
commit 3f7632b762
6 changed files with 327 additions and 620 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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 <<USAGE
Usage: $0 -S SERVER -U USER -P PASSWORD [options]
Options:
--path NETWARE_PATH NetWare file path to create/delete (default: $NETWARE_PATH)
--unix-path UNIX_PATH Unix path corresponding to --path (default: $UNIX_PATH)
--volume-root DIR Unix volume root; avoids deriving it from --unix-path
--deleted-by NAME Expected directory below .recycle/.salvage (default: USER)
--recycle-name NAME Recycle repository name (default: $RECYCLE_REPOSITORY)
--salvage-name NAME Salvage metadata repository name (default: $SALVAGE_REPOSITORY)
--out FILE Write the complete report to FILE as well as stdout
--no-clean-artifacts Do not remove stale .recycle/.salvage test artifacts before NCP delete
--stop-on-failure Stop after the first failing check
-h, --help Show this help
The script uses tests/salvage/ncp_delete_smoke to create and delete the file
through classic NCP requests. It never removes the live file through local Unix
unlink/rm; local filesystem access is only used after the NCP delete to inspect
.recycle and .salvage.
USAGE
}
while [ $# -gt 0 ]; do
case "$1" in
-S|--server)
SERVER=$2; shift 2 ;;
-U|--user)
USER_NAME=$2; shift 2 ;;
-P|--password)
PASSWORD=$2; shift 2 ;;
--path)
NETWARE_PATH=$2; shift 2 ;;
--unix-path)
UNIX_PATH=$2; shift 2 ;;
--volume-root)
VOLUME_ROOT=$2; shift 2 ;;
--deleted-by)
DELETED_BY=$2; shift 2 ;;
--recycle-name)
RECYCLE_REPOSITORY=$2; shift 2 ;;
--salvage-name)
SALVAGE_REPOSITORY=$2; shift 2 ;;
--out)
OUT_FILE=$2; shift 2 ;;
--no-clean-artifacts)
CLEAN_ARTIFACTS=0; shift ;;
--stop-on-failure)
KEEP_GOING=0; shift ;;
-h|--help)
usage; exit 0 ;;
*)
echo "Unknown argument: $1" >&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 ]

View File

@@ -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 <<USAGE
Usage: $0 -S SERVER -U USER -P PASSWORD [options]
Options:
--path NETWARE_PATH NetWare file path to create/delete twice
--unix-path UNIX_PATH Unix path corresponding to --path
--volume-root PATH Unix root of the NetWare volume
--deleted-by NAME Expected directory below .recycle/.salvage (default: USER)
--recycle-name NAME Recycle repository name (default: $RECYCLE_REPOSITORY)
--salvage-name NAME Salvage metadata repository name (default: $SALVAGE_REPOSITORY)
--out FILE Write the complete report to FILE as well as stdout
-h, --help Show this help
The script expects salvage option 53 to include the 'v' versions flag. It
creates and deletes the same NetWare path twice through ncp_delete_smoke and
then checks for the Samba-compatible second name: Copy #1 of NAME.
USAGE
}
while [ $# -gt 0 ]; do
case "$1" in
-S|--server) SERVER=$2; shift 2 ;;
-U|--user) USER_NAME=$2; shift 2 ;;
-P|--password) PASSWORD=$2; shift 2 ;;
--path) NETWARE_PATH=$2; shift 2 ;;
--unix-path) UNIX_PATH=$2; shift 2 ;;
--volume-root) VOLUME_ROOT=$2; shift 2 ;;
--deleted-by) DELETED_BY=$2; shift 2 ;;
--recycle-name) RECYCLE_REPOSITORY=$2; shift 2 ;;
--salvage-name) SALVAGE_REPOSITORY=$2; shift 2 ;;
--out) OUT_FILE=$2; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown argument: $1" >&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 ]

View File

@@ -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 <<USAGE
Usage: $0 -S SERVER -U USER -P PASSWORD [options]
Options:
--path NETWARE_PATH NetWare file path to create/delete twice (default: $NETWARE_PATH)
--unix-path UNIX_PATH Unix path corresponding to --path (default: $UNIX_PATH)
--volume-root DIR Unix volume root; avoids deriving it from --unix-path
--deleted-by NAME Expected directory below .recycle/.salvage (default: USER)
--recycle-name NAME Recycle repository name (default: $RECYCLE_REPOSITORY)
--salvage-name NAME Salvage metadata repository name (default: $SALVAGE_REPOSITORY)
--out FILE Write the complete report to FILE as well as stdout
--no-clean-artifacts Do not try to remove stale test artifacts first
--stop-on-failure Stop after the first failing check
-h, --help Show this help
The suite uses tests/salvage/ncp_delete_smoke to create and delete the file
through classic NCP requests. It never removes the live file through local Unix
unlink/rm; local filesystem access is only used to inspect .recycle/.salvage and
for best-effort cleanup of stale test artifacts. Cleanup failures are warnings
because the server may have created those files as another Unix user.
The history phase expects salvage option 53 to include the 'v' versions flag.
With the default 'kv' flags, the second deleted copy should be stored as:
Copy #1 of FILENAME
USAGE
}
while [ $# -gt 0 ]; do
case "$1" in
-S|--server) SERVER=$2; shift 2 ;;
-U|--user) USER_NAME=$2; shift 2 ;;
-P|--password) PASSWORD=$2; shift 2 ;;
--path) NETWARE_PATH=$2; PATH_SET=1; shift 2 ;;
--unix-path) UNIX_PATH=$2; UNIX_PATH_SET=1; shift 2 ;;
--volume-root) VOLUME_ROOT=$2; shift 2 ;;
--deleted-by) DELETED_BY=$2; shift 2 ;;
--recycle-name) RECYCLE_REPOSITORY=$2; shift 2 ;;
--salvage-name) SALVAGE_REPOSITORY=$2; shift 2 ;;
--out) OUT_FILE=$2; shift 2 ;;
--no-clean-artifacts) CLEAN_ARTIFACTS=0; shift ;;
--stop-on-failure) KEEP_GOING=0; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown argument: $1" >&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 ]