Files
obsidian-livesync/src/apps/cli/test/test-p2p-three-nodes-conflict-linux.sh
vorotamoroz 6c69547cef ### Fixed
- Fixed flaky timing issues in P2P synchronisation.
- Fixed more binary file handling issues in CLI.

### Tests

- Rewrite P2P end-to-end tests to use the CLI as host.
2026-03-16 00:48:22 +09:00

243 lines
8.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
cd "$CLI_DIR"
source "$SCRIPT_DIR/test-helpers.sh"
display_test_info
RUN_BUILD="${RUN_BUILD:-1}"
KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}"
VERBOSE_TEST_LOGGING="${VERBOSE_TEST_LOGGING:-0}"
RELAY="${RELAY:-ws://localhost:4000/}"
USE_INTERNAL_RELAY="${USE_INTERNAL_RELAY:-1}"
ROOM_ID_PREFIX="${ROOM_ID_PREFIX:-p2p-room}"
PASSPHRASE_PREFIX="${PASSPHRASE_PREFIX:-p2p-pass}"
APP_ID="${APP_ID:-self-hosted-livesync-cli-tests}"
PEERS_TIMEOUT="${PEERS_TIMEOUT:-10}"
SYNC_TIMEOUT="${SYNC_TIMEOUT:-15}"
ROOM_ID="${ROOM_ID_PREFIX}-$(date +%s)-$RANDOM-$RANDOM"
PASSPHRASE="${PASSPHRASE_PREFIX}-$(date +%s)-$RANDOM-$RANDOM"
cli_test_init_cli_cmd
if [[ "$RUN_BUILD" == "1" ]]; then
echo "[INFO] building CLI"
npm run build
fi
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-p2p-3nodes.XXXXXX")"
VAULT_A="$WORK_DIR/vault-a"
VAULT_B="$WORK_DIR/vault-b"
VAULT_C="$WORK_DIR/vault-c"
SETTINGS_A="$WORK_DIR/settings-a.json"
SETTINGS_B="$WORK_DIR/settings-b.json"
SETTINGS_C="$WORK_DIR/settings-c.json"
HOST_LOG="$WORK_DIR/p2p-host.log"
mkdir -p "$VAULT_A" "$VAULT_B" "$VAULT_C"
cleanup() {
local exit_code=$?
if [[ -n "${HOST_PID:-}" ]] && kill -0 "$HOST_PID" >/dev/null 2>&1; then
kill -TERM "$HOST_PID" >/dev/null 2>&1 || true
wait "$HOST_PID" >/dev/null 2>&1 || true
fi
if [[ "${P2P_RELAY_STARTED:-0}" == "1" ]]; then
cli_test_stop_p2p_relay
fi
if [[ "$KEEP_TEST_DATA" != "1" ]]; then
rm -rf "$WORK_DIR"
else
echo "[INFO] KEEP_TEST_DATA=1, preserving artefacts at $WORK_DIR"
fi
exit "$exit_code"
}
trap cleanup EXIT
if [[ "$USE_INTERNAL_RELAY" == "1" ]]; then
if cli_test_is_local_p2p_relay "$RELAY"; then
cli_test_start_p2p_relay
P2P_RELAY_STARTED=1
else
echo "[INFO] USE_INTERNAL_RELAY=1 but RELAY is not local ($RELAY), skipping local relay startup"
fi
fi
run_cli_a() {
run_cli "$VAULT_A" --settings "$SETTINGS_A" "$@"
}
run_cli_b() {
run_cli "$VAULT_B" --settings "$SETTINGS_B" "$@"
}
run_cli_c() {
run_cli "$VAULT_C" --settings "$SETTINGS_C" "$@"
}
echo "[INFO] preparing settings"
echo "[INFO] relay=$RELAY room=$ROOM_ID app=$APP_ID"
cli_test_init_settings_file "$SETTINGS_A"
cli_test_init_settings_file "$SETTINGS_B"
cli_test_init_settings_file "$SETTINGS_C"
cli_test_apply_p2p_settings "$SETTINGS_A" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY"
cli_test_apply_p2p_settings "$SETTINGS_B" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY"
cli_test_apply_p2p_settings "$SETTINGS_C" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY"
echo "[CASE] start p2p-host on A"
run_cli_a p2p-host >"$HOST_LOG" 2>&1 &
HOST_PID=$!
for _ in 1 2 3 4 5 6 7 8 9 10; do
echo "[INFO] waiting for p2p-host to start..."
if grep -Fq "P2P host is running" "$HOST_LOG"; then
break
fi
sleep 1
done
if ! grep -Fq "P2P host is running" "$HOST_LOG"; then
echo "[FAIL] p2p-host did not become ready" >&2
cat "$HOST_LOG" >&2
exit 1
fi
echo "[PASS] p2p-host started"
echo "[CASE] discover host peer from B"
PEERS_FROM_B="$(run_cli_b p2p-peers "$PEERS_TIMEOUT")"
HOST_PEER_ID="$(awk -F $'\t' 'NF>=3 && $1=="[peer]" {print $2; exit}' <<< "$PEERS_FROM_B")"
if [[ -z "$HOST_PEER_ID" ]]; then
echo "[FAIL] B could not find host peer" >&2
echo "$PEERS_FROM_B" >&2
exit 1
fi
echo "[PASS] B discovered host peer: $HOST_PEER_ID"
echo "[CASE] discover host peer from C"
PEERS_FROM_C="$(run_cli_c p2p-peers "$PEERS_TIMEOUT")"
HOST_PEER_ID_FROM_C="$(awk -F $'\t' 'NF>=3 && $1=="[peer]" {print $2; exit}' <<< "$PEERS_FROM_C")"
if [[ -z "$HOST_PEER_ID_FROM_C" ]]; then
echo "[FAIL] C could not find host peer" >&2
echo "$PEERS_FROM_C" >&2
exit 1
fi
echo "[PASS] C discovered host peer: $HOST_PEER_ID_FROM_C"
TARGET_PATH="p2p/conflicted-from-two-clients.txt"
echo "[CASE] B creates file and syncs"
printf 'from-client-b-v1\n' | run_cli_b put "$TARGET_PATH" >/dev/null
run_cli_b p2p-sync "$HOST_PEER_ID" "$SYNC_TIMEOUT" >/dev/null
echo "[CASE] C syncs and can see B file"
run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null
VISIBLE_ON_C=""
for _ in 1 2 3 4 5; do
if VISIBLE_ON_C="$(run_cli_c cat "$TARGET_PATH" 2>/dev/null | cli_test_sanitise_cat_stdout)"; then
if [[ "$VISIBLE_ON_C" == "from-client-b-v1" ]]; then
break
fi
fi
run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null
sleep 1
done
cli_test_assert_equal "from-client-b-v1" "$VISIBLE_ON_C" "C should see file created by B"
echo "[CASE] B and C modify file independently"
printf 'from-client-b-v2\n' | run_cli_b put "$TARGET_PATH" >/dev/null
printf 'from-client-c-v2\n' | run_cli_c put "$TARGET_PATH" >/dev/null
echo "[CASE] B and C sync to host concurrently"
set +e
run_cli_b p2p-sync "$HOST_PEER_ID" "$SYNC_TIMEOUT" >/dev/null &
SYNC_B_PID=$!
run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null &
SYNC_C_PID=$!
wait "$SYNC_B_PID"
SYNC_B_EXIT=$?
wait "$SYNC_C_PID"
SYNC_C_EXIT=$?
set -e
if [[ "$SYNC_B_EXIT" -ne 0 || "$SYNC_C_EXIT" -ne 0 ]]; then
echo "[FAIL] concurrent sync failed: B=$SYNC_B_EXIT C=$SYNC_C_EXIT" >&2
exit 1
fi
echo "[CASE] sync back to clients"
run_cli_b p2p-sync "$HOST_PEER_ID" "$SYNC_TIMEOUT" >/dev/null
run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null
echo "[CASE] B info shows conflict"
INFO_JSON_B_BEFORE="$(run_cli_b info "$TARGET_PATH")"
CONFLICTS_B_BEFORE="$(printf '%s' "$INFO_JSON_B_BEFORE" | cli_test_json_string_field_from_stdin conflicts)"
KEEP_REV_B="$(printf '%s' "$INFO_JSON_B_BEFORE" | cli_test_json_string_field_from_stdin revision)"
if [[ "$CONFLICTS_B_BEFORE" == "N/A" || -z "$CONFLICTS_B_BEFORE" ]]; then
echo "[FAIL] expected conflicts on B after two-client sync" >&2
echo "$INFO_JSON_B_BEFORE" >&2
exit 1
fi
if [[ -z "$KEEP_REV_B" ]]; then
echo "[FAIL] could not read current revision on B for resolve" >&2
echo "$INFO_JSON_B_BEFORE" >&2
exit 1
fi
echo "[PASS] conflict detected on B"
echo "[CASE] C info shows conflict"
INFO_JSON_C_BEFORE="$(run_cli_c info "$TARGET_PATH")"
CONFLICTS_C_BEFORE="$(printf '%s' "$INFO_JSON_C_BEFORE" | cli_test_json_string_field_from_stdin conflicts)"
KEEP_REV_C="$(printf '%s' "$INFO_JSON_C_BEFORE" | cli_test_json_string_field_from_stdin revision)"
if [[ "$CONFLICTS_C_BEFORE" == "N/A" || -z "$CONFLICTS_C_BEFORE" ]]; then
echo "[FAIL] expected conflicts on C after two-client sync" >&2
echo "$INFO_JSON_C_BEFORE" >&2
exit 1
fi
if [[ -z "$KEEP_REV_C" ]]; then
echo "[FAIL] could not read current revision on C for resolve" >&2
echo "$INFO_JSON_C_BEFORE" >&2
exit 1
fi
echo "[PASS] conflict detected on C"
echo "[CASE] resolve conflict on B and C"
run_cli_b resolve "$TARGET_PATH" "$KEEP_REV_B" >/dev/null
run_cli_c resolve "$TARGET_PATH" "$KEEP_REV_C" >/dev/null
INFO_JSON_B_AFTER="$(run_cli_b info "$TARGET_PATH")"
CONFLICTS_B_AFTER="$(printf '%s' "$INFO_JSON_B_AFTER" | cli_test_json_string_field_from_stdin conflicts)"
if [[ "$CONFLICTS_B_AFTER" != "N/A" ]]; then
echo "[FAIL] conflict still remains on B after resolve" >&2
echo "$INFO_JSON_B_AFTER" >&2
exit 1
fi
INFO_JSON_C_AFTER="$(run_cli_c info "$TARGET_PATH")"
CONFLICTS_C_AFTER="$(printf '%s' "$INFO_JSON_C_AFTER" | cli_test_json_string_field_from_stdin conflicts)"
if [[ "$CONFLICTS_C_AFTER" != "N/A" ]]; then
echo "[FAIL] conflict still remains on C after resolve" >&2
echo "$INFO_JSON_C_AFTER" >&2
exit 1
fi
FINAL_CONTENT_B="$(run_cli_b cat "$TARGET_PATH" | cli_test_sanitise_cat_stdout)"
FINAL_CONTENT_C="$(run_cli_c cat "$TARGET_PATH" | cli_test_sanitise_cat_stdout)"
if [[ "$FINAL_CONTENT_B" != "from-client-b-v2" && "$FINAL_CONTENT_B" != "from-client-c-v2" ]]; then
echo "[FAIL] unexpected final content on B after resolve" >&2
echo "[FAIL] final content on B: $FINAL_CONTENT_B" >&2
exit 1
fi
if [[ "$FINAL_CONTENT_C" != "from-client-b-v2" && "$FINAL_CONTENT_C" != "from-client-c-v2" ]]; then
echo "[FAIL] unexpected final content on C after resolve" >&2
echo "[FAIL] final content on C: $FINAL_CONTENT_C" >&2
exit 1
fi
echo "[PASS] conflicts resolved on B and C"
echo "[PASS] all 3-node P2P conflict scenarios passed"