diff --git a/cli-e2e/bb.edn b/cli-e2e/bb.edn index 2c76216866..02b811cbfd 100644 --- a/cli-e2e/bb.edn +++ b/cli-e2e/bb.edn @@ -13,7 +13,8 @@ :help :boolean :dry-run :boolean :skip-build :boolean - :verbose :boolean}})) + :verbose :boolean + :timings :boolean}})) unit-test {:doc "Run internal cli-e2e harness unit tests" diff --git a/cli-e2e/scripts/db_sync_server.py b/cli-e2e/scripts/db_sync_server.py index ac573062dc..095a60b31e 100644 --- a/cli-e2e/scripts/db_sync_server.py +++ b/cli-e2e/scripts/db_sync_server.py @@ -8,6 +8,7 @@ import base64 import json import os import signal +import socket import subprocess import sys import time @@ -103,7 +104,7 @@ def wait_health(base_url: str, timeout_s: float, interval_s: float) -> bool: with urllib.request.urlopen(url, timeout=2) as response: if response.status == 200: return True - except (urllib.error.URLError, TimeoutError): + except (urllib.error.URLError, TimeoutError, socket.timeout): pass time.sleep(interval_s) return False diff --git a/cli-e2e/spec/sync_cases.edn b/cli-e2e/spec/sync_cases.edn index 84a1c793e2..02cbb449ea 100644 --- a/cli-e2e/spec/sync_cases.edn +++ b/cli-e2e/spec/sync_cases.edn @@ -1,369 +1,298 @@ -[ - {:id "sync-bootstrap-upload-download-a-to-b" - :graph "sync-e2e-bootstrap-a-to-b" - :vars {:sync-port "18080" - :sync-http-base "http://127.0.0.1:18080" - :sync-ws-url "ws://127.0.0.1:18080/sync/%s" - :marker-content "sync-happy-bootstrap-a-to-b-marker" - :home-dir "{{tmp-dir}}/home" - :auth-path "{{tmp-dir}}/home/logseq/auth.json" - :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"} - :setup ["mkdir -p '{{tmp-dir}}/graphs-b'" - "mkdir -p '{{tmp-dir}}/home/logseq'" - "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncBootstrapHome >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncBootstrapHome --content '{{marker-content}}' >/dev/null"] - :cmds ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{marker-content}}\")] ]' --require-result" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"] - :expect {:exit 0 - :stdout-json-paths {[:status] "ok" - [:data :pending-local] 0 - [:data :pending-asset] 0 - [:data :pending-server] 0 - [:data :last-error] nil}} - :covers {:commands ["sync upload" - "sync download" - "sync status"] - :options {:global ["--config" - "--graph" - "--data-dir" - "--output"]}} - :cleanup ["{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" - "{{cli}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json server stop --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' stop --pid-file '{{tmp-dir}}/db-sync-server.pid'"] - :tags [:sync :happy-path :bootstrap :a-to-b]} - - {:id "sync-incremental-update-a-to-b" - :graph "sync-e2e-incremental-a-to-b" - :vars {:sync-port "18080" - :sync-http-base "http://127.0.0.1:18080" - :sync-ws-url "ws://127.0.0.1:18080/sync/%s" - :seed-marker "sync-happy-incremental-seed-marker" - :incremental-marker "sync-happy-incremental-update-marker" - :home-dir "{{tmp-dir}}/home" - :auth-path "{{tmp-dir}}/home/logseq/auth.json" - :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"} - :setup ["mkdir -p '{{tmp-dir}}/graphs-b'" - "mkdir -p '{{tmp-dir}}/home/logseq'" - "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncIncrementalHome >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncIncrementalHome --content '{{seed-marker}}' >/dev/null"] - :cmds ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncIncrementalHome --content '{{incremental-marker}}' >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{incremental-marker}}\")] ]' --require-result" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"] - :expect {:exit 0 - :stdout-json-paths {[:status] "ok" - [:data :pending-local] 0 - [:data :pending-asset] 0 - [:data :pending-server] 0 - [:data :last-error] nil}} - :covers {:commands ["sync upload" - "sync download" - "sync status"] - :options {:global ["--config" - "--graph" - "--data-dir" - "--output"]}} - :cleanup ["{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" - "{{cli}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json server stop --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' stop --pid-file '{{tmp-dir}}/db-sync-server.pid'"] - :tags [:sync :happy-path :incremental :a-to-b]} - - {:id "sync-bidirectional-roundtrip" - :graph "sync-e2e-bidirectional-roundtrip" - :vars {:sync-port "18080" - :sync-http-base "http://127.0.0.1:18080" - :sync-ws-url "ws://127.0.0.1:18080/sync/%s" - :marker-a "sync-happy-roundtrip-a-seed-marker" - :marker-b "sync-happy-roundtrip-b-origin-marker" - :home-dir "{{tmp-dir}}/home" - :auth-path "{{tmp-dir}}/home/logseq/auth.json" - :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"} - :setup ["mkdir -p '{{tmp-dir}}/graphs-b'" - "mkdir -p '{{tmp-dir}}/home/logseq'" - "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncRoundtripAHome >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncRoundtripAHome --content '{{marker-a}}' >/dev/null"] - :cmds ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json upsert page --graph {{graph-arg}} --page SyncRoundtripBHome >/dev/null" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json upsert block --graph {{graph-arg}} --target-page SyncRoundtripBHome --content '{{marker-b}}' >/dev/null" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync upload --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{marker-b}}\")] ]' --require-result" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync status --graph {{graph-arg}}"] - :expect {:exit 0 - :stdout-json-paths {[:status] "ok" - [:data :pending-local] 0 - [:data :pending-asset] 0 - [:data :pending-server] 0 - [:data :last-error] nil}} - :covers {:commands ["sync upload" - "sync download" - "sync status"] - :options {:global ["--config" - "--graph" - "--data-dir" - "--output"]}} - :cleanup ["{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" - "{{cli}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json server stop --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' stop --pid-file '{{tmp-dir}}/db-sync-server.pid'"] - :tags [:sync :happy-path :bidirectional :roundtrip]} - - {:id "sync-multi-batch-operations" - :graph "sync-e2e-multi-batch-operations" - :vars {:sync-port "18080" - :sync-http-base "http://127.0.0.1:18080" - :sync-ws-url "ws://127.0.0.1:18080/sync/%s" - :seed-marker "sync-happy-multi-batch-seed-marker" - :batch-marker-1 "sync-happy-multi-batch-marker-1" - :batch-marker-2 "sync-happy-multi-batch-marker-2" - :batch-marker-3 "sync-happy-multi-batch-marker-3" - :home-dir "{{tmp-dir}}/home" - :auth-path "{{tmp-dir}}/home/logseq/auth.json" - :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"} - :setup ["mkdir -p '{{tmp-dir}}/graphs-b'" - "mkdir -p '{{tmp-dir}}/home/logseq'" - "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncMultiBatchHome >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncMultiBatchHome --content '{{seed-marker}}' >/dev/null"] - :cmds ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncMultiBatchOne >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncMultiBatchOne --content '{{batch-marker-1}}' >/dev/null" - "sleep 1" - "{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncMultiBatchTwo >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncMultiBatchTwo --content '{{batch-marker-2}}' >/dev/null" - "sleep 1" - "{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncMultiBatchThree >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncMultiBatchThree --content '{{batch-marker-3}}' >/dev/null" - "sleep 1" - "{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{batch-marker-1}}\")] ]' --require-result" - "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{batch-marker-2}}\")] ]' --require-result" - "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{batch-marker-3}}\")] ]' --require-result" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"] - :expect {:exit 0 - :stdout-json-paths {[:status] "ok" - [:data :pending-local] 0 - [:data :pending-asset] 0 - [:data :pending-server] 0 - [:data :last-error] nil}} - :covers {:commands ["sync upload" - "sync download" - "sync status"] - :options {:global ["--config" - "--graph" - "--data-dir" - "--output"]}} - :cleanup ["{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" - "{{cli}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json server stop --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' stop --pid-file '{{tmp-dir}}/db-sync-server.pid'"] - :tags [:sync :happy-path :multi-batch :a-to-b]} - - {:id "sync-status-steady-state" - :graph "sync-e2e-status-steady-state" - :vars {:sync-port "18080" - :sync-http-base "http://127.0.0.1:18080" - :sync-ws-url "ws://127.0.0.1:18080/sync/%s" - :marker-content "sync-happy-steady-state-marker" - :home-dir "{{tmp-dir}}/home" - :auth-path "{{tmp-dir}}/home/logseq/auth.json" - :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"} - :setup ["mkdir -p '{{tmp-dir}}/graphs-b'" - "mkdir -p '{{tmp-dir}}/home/logseq'" - "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncSteadyStateHome >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncSteadyStateHome --content '{{marker-content}}' >/dev/null"] - :cmds ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{marker-content}}\")] ]' --require-result" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 30 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 30 --interval-s 1" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"] - :expect {:exit 0 - :stdout-json-paths {[:status] "ok" - [:data :pending-local] 0 - [:data :pending-asset] 0 - [:data :pending-server] 0 - [:data :last-error] nil}} - :covers {:commands ["sync upload" - "sync download" - "sync status"] - :options {:global ["--config" - "--graph" - "--data-dir" - "--output"]}} - :cleanup ["{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" - "{{cli}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json server stop --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' stop --pid-file '{{tmp-dir}}/db-sync-server.pid'"] - :tags [:sync :happy-path :steady-state :status]} - - {:id "sync-random-bidirectional-block-ops" - :graph "sync-e2e-random-bidirectional-block-ops" - :vars {:sync-port "18080" - :sync-http-base "http://127.0.0.1:18080" - :sync-ws-url "ws://127.0.0.1:18080/sync/%s" - :random-page "SyncRandomOpsHome" - :rounds-per-client "100" - :random-seed "424242" - :home-dir "{{tmp-dir}}/home" - :auth-path "{{tmp-dir}}/home/logseq/auth.json" - :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"} - :setup ["mkdir -p '{{tmp-dir}}/graphs-b'" - "mkdir -p '{{tmp-dir}}/home/logseq'" - "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page '{{random-page}}' >/dev/null"] - :cmds ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/random_bidirectional_block_ops.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --page '{{random-page}}' --rounds-per-client {{rounds-per-client}} --seed {{random-seed}}" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 180 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 180 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find (pull ?b [:block/uuid :block/title :block/order {:block/parent [:block/uuid]}]) :where [?p :block/title \"{{random-page}}\"] [?b :block/page ?p] [?b :block/uuid]]' --require-result" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"] - :expect {:exit 0 - :stdout-json-paths {[:status] "ok" - [:data :pending-local] 0 - [:data :pending-asset] 0 - [:data :pending-server] 0 - [:data :last-error] nil}} - :covers {:commands ["sync upload" - "sync download" - "sync status"] - :options {:global ["--config" - "--graph" - "--data-dir" - "--output"]}} - :cleanup ["{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" - "{{cli}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json server stop --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' stop --pid-file '{{tmp-dir}}/db-sync-server.pid'"] - :tags [:sync :stress :bidirectional :random :block-ops]} - - {:id "sync-offline-random-bidirectional-block-ops" - :graph "sync-e2e-offline-random-bidirectional-block-ops" - :vars {:sync-port "18080" - :sync-http-base "http://127.0.0.1:18080" - :sync-ws-url "ws://127.0.0.1:18080/sync/%s" - :random-page "SyncOfflineRandomOpsHome" - :seed-marker "sync-offline-random-seed-marker" - :rounds-per-client "100" - :random-seed "989898" - :home-dir "{{tmp-dir}}/home" - :auth-path "{{tmp-dir}}/home/logseq/auth.json" - :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"} - :setup ["mkdir -p '{{tmp-dir}}/graphs-b'" - "mkdir -p '{{tmp-dir}}/home/logseq'" - "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page '{{random-page}}' >/dev/null" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page '{{random-page}}' --content '{{seed-marker}}' >/dev/null"] - :cmds ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{seed-marker}}\")] ]' --require-result" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync stop --graph {{graph-arg}}" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync stop --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/random_bidirectional_block_ops.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --page '{{random-page}}' --rounds-per-client {{rounds-per-client}} --seed {{random-seed}}" - "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 240 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 240 --interval-s 1" - "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find (pull ?b [:block/uuid :block/title :block/order {:block/parent [:block/uuid]}]) :where [?p :block/title \"{{random-page}}\"] [?b :block/page ?p] [?b :block/uuid]]' --require-result" - "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"] - :expect {:exit 0 - :stdout-json-paths {[:status] "ok" - [:data :pending-local] 0 - [:data :pending-asset] 0 - [:data :pending-server] 0 - [:data :last-error] nil}} - :covers {:commands ["sync upload" - "sync download" - "sync status"] - :options {:global ["--config" - "--graph" - "--data-dir" - "--output"]}} - :cleanup ["{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" - "{{cli}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json server stop --graph {{graph-arg}}" - "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' stop --pid-file '{{tmp-dir}}/db-sync-server.pid'"] - :tags [:sync :stress :offline :bidirectional :random :block-ops]}] +{:templates + #:sync{:default + {:expect + {:exit 0, + :stdout-json-paths + {[:status] "ok", + [:data :pending-local] 0, + [:data :pending-asset] 0, + [:data :pending-server] 0, + [:data :last-error] nil}}, + :covers + {:commands ["sync upload" "sync download" "sync status"], + :options + {:global ["--config" "--graph" "--data-dir" "--output"]}}, + :cleanup + ["{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" + "{{cli}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json server stop --graph {{graph-arg}}" + "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' stop --pid-file '{{tmp-dir}}/db-sync-server.pid'"], + :tags [:sync]}}, + :cases + [{:tags [:happy-path :bootstrap :a-to-b], + :extends :sync/default, + :setup + ["mkdir -p '{{tmp-dir}}/graphs-b'" + "mkdir -p '{{tmp-dir}}/home/logseq'" + "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncBootstrapHome >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncBootstrapHome --content '{{marker-content}}' >/dev/null"], + :cmds + ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{marker-content}}\")] ]' --require-result" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"], + :id "sync-bootstrap-upload-download-a-to-b", + :graph "sync-e2e-bootstrap-a-to-b", + :vars + {:sync-port "18080", + :sync-http-base "http://127.0.0.1:18080", + :sync-ws-url "ws://127.0.0.1:18080/sync/%s", + :marker-content "sync-happy-bootstrap-a-to-b-marker", + :home-dir "{{tmp-dir}}/home", + :auth-path "{{tmp-dir}}/home/logseq/auth.json", + :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"}} + {:tags [:happy-path :incremental :a-to-b], + :extends :sync/default, + :setup + ["mkdir -p '{{tmp-dir}}/graphs-b'" + "mkdir -p '{{tmp-dir}}/home/logseq'" + "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncIncrementalHome >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncIncrementalHome --content '{{seed-marker}}' >/dev/null"], + :cmds + ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncIncrementalHome --content '{{incremental-marker}}' >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{incremental-marker}}\")] ]' --require-result" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"], + :id "sync-incremental-update-a-to-b", + :graph "sync-e2e-incremental-a-to-b", + :vars + {:sync-port "18080", + :sync-http-base "http://127.0.0.1:18080", + :sync-ws-url "ws://127.0.0.1:18080/sync/%s", + :seed-marker "sync-happy-incremental-seed-marker", + :incremental-marker "sync-happy-incremental-update-marker", + :home-dir "{{tmp-dir}}/home", + :auth-path "{{tmp-dir}}/home/logseq/auth.json", + :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"}} + {:tags [:happy-path :bidirectional :roundtrip], + :extends :sync/default, + :setup + ["mkdir -p '{{tmp-dir}}/graphs-b'" + "mkdir -p '{{tmp-dir}}/home/logseq'" + "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncRoundtripAHome >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncRoundtripAHome --content '{{marker-a}}' >/dev/null"], + :cmds + ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json upsert page --graph {{graph-arg}} --page SyncRoundtripBHome >/dev/null" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json upsert block --graph {{graph-arg}} --target-page SyncRoundtripBHome --content '{{marker-b}}' >/dev/null" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync upload --graph {{graph-arg}}" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{marker-b}}\")] ]' --require-result" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync status --graph {{graph-arg}}"], + :id "sync-bidirectional-roundtrip", + :graph "sync-e2e-bidirectional-roundtrip", + :vars + {:sync-port "18080", + :sync-http-base "http://127.0.0.1:18080", + :sync-ws-url "ws://127.0.0.1:18080/sync/%s", + :marker-a "sync-happy-roundtrip-a-seed-marker", + :marker-b "sync-happy-roundtrip-b-origin-marker", + :home-dir "{{tmp-dir}}/home", + :auth-path "{{tmp-dir}}/home/logseq/auth.json", + :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"}} + {:tags [:happy-path :multi-batch :a-to-b], + :extends :sync/default, + :setup + ["mkdir -p '{{tmp-dir}}/graphs-b'" + "mkdir -p '{{tmp-dir}}/home/logseq'" + "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncMultiBatchHome >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncMultiBatchHome --content '{{seed-marker}}' >/dev/null"], + :cmds + ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncMultiBatchOne >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncMultiBatchOne --content '{{batch-marker-1}}' >/dev/null" + "sleep 1" + "{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncMultiBatchTwo >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncMultiBatchTwo --content '{{batch-marker-2}}' >/dev/null" + "sleep 1" + "{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncMultiBatchThree >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncMultiBatchThree --content '{{batch-marker-3}}' >/dev/null" + "sleep 1" + "{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{batch-marker-1}}\")] ]' --require-result" + "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{batch-marker-2}}\")] ]' --require-result" + "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{batch-marker-3}}\")] ]' --require-result" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"], + :id "sync-multi-batch-operations", + :graph "sync-e2e-multi-batch-operations", + :vars + {:sync-port "18080", + :seed-marker "sync-happy-multi-batch-seed-marker", + :sync-http-base "http://127.0.0.1:18080", + :batch-marker-3 "sync-happy-multi-batch-marker-3", + :auth-path "{{tmp-dir}}/home/logseq/auth.json", + :batch-marker-1 "sync-happy-multi-batch-marker-1", + :home-dir "{{tmp-dir}}/home", + :sync-ws-url "ws://127.0.0.1:18080/sync/%s", + :batch-marker-2 "sync-happy-multi-batch-marker-2", + :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"}} + {:tags [:happy-path :steady-state :status], + :extends :sync/default, + :setup + ["mkdir -p '{{tmp-dir}}/graphs-b'" + "mkdir -p '{{tmp-dir}}/home/logseq'" + "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page SyncSteadyStateHome >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page SyncSteadyStateHome --content '{{marker-content}}' >/dev/null"], + :cmds + ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{marker-content}}\")] ]' --require-result" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 30 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 30 --interval-s 1" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"], + :id "sync-status-steady-state", + :graph "sync-e2e-status-steady-state", + :vars + {:sync-port "18080", + :sync-http-base "http://127.0.0.1:18080", + :sync-ws-url "ws://127.0.0.1:18080/sync/%s", + :marker-content "sync-happy-steady-state-marker", + :home-dir "{{tmp-dir}}/home", + :auth-path "{{tmp-dir}}/home/logseq/auth.json", + :cli-home "HOME='{{tmp-dir}}/home' {{cli}}"}} + {:tags [:stress :bidirectional :random :block-ops], + :extends :sync/default, + :setup + ["mkdir -p '{{tmp-dir}}/graphs-b'" + "mkdir -p '{{tmp-dir}}/home/logseq'" + "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page '{{random-page}}' >/dev/null"], + :cmds + ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/random_bidirectional_block_ops.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --page '{{random-page}}' --rounds-per-client {{rounds-per-client}} --seed {{random-seed}}" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 180 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 180 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find (pull ?b [:block/uuid :block/title :block/order {:block/parent [:block/uuid]}]) :where [?p :block/title \"{{random-page}}\"] [?b :block/page ?p] [?b :block/uuid]]' --require-result" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"], + :id "sync-random-bidirectional-block-ops", + :graph "sync-e2e-random-bidirectional-block-ops", + :vars + {:sync-port "18080", + :sync-http-base "http://127.0.0.1:18080", + :random-seed "424242", + :random-page "SyncRandomOpsHome", + :auth-path "{{tmp-dir}}/home/logseq/auth.json", + :home-dir "{{tmp-dir}}/home", + :sync-ws-url "ws://127.0.0.1:18080/sync/%s", + :cli-home "HOME='{{tmp-dir}}/home' {{cli}}", + :rounds-per-client "100"}} + {:tags [:stress :offline :bidirectional :random :block-ops], + :extends :sync/default, + :setup + ["mkdir -p '{{tmp-dir}}/graphs-b'" + "mkdir -p '{{tmp-dir}}/home/logseq'" + "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{tmp-dir}}/cli-b.edn' --auth-path '{{auth-path}}' --http-base '{{sync-http-base}}' --ws-url '{{sync-ws-url}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --repo-root '{{repo-root}}' --pid-file '{{tmp-dir}}/db-sync-server.pid' --log-file '{{tmp-dir}}/db-sync-server.log' --data-dir '{{tmp-dir}}/db-sync-server-data' --port {{sync-port}} --auth-path '{{auth-path}}'" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync ensure-keys --graph {{graph-arg}} --e2ee-password '11111' --upload-keys" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert page --graph {{graph-arg}} --page '{{random-page}}' >/dev/null" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json upsert block --graph {{graph-arg}} --target-page '{{random-page}}' --content '{{seed-marker}}' >/dev/null"], + :cmds + ["{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync upload --graph {{graph-arg}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync download --graph {{graph-arg}} --e2ee-password '11111'" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 120 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find ?title :where [?b :block/title ?title] [(= ?title \"{{seed-marker}}\")] ]' --require-result" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync stop --graph {{graph-arg}}" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync stop --graph {{graph-arg}}" + "python3 '{{repo-root}}/cli-e2e/scripts/random_bidirectional_block_ops.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --page '{{random-page}}' --rounds-per-client {{rounds-per-client}} --seed {{random-seed}}" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync start --graph {{graph-arg}} --e2ee-password '11111'" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{data-dir}}' --config '{{config-path}}' --graph '{{graph}}' --timeout-s 240 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/wait_sync_status.py' --cli '{{repo-root}}/static/logseq-cli.js' --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --graph '{{graph}}' --timeout-s 240 --interval-s 1" + "python3 '{{repo-root}}/cli-e2e/scripts/compare_graph_queries.py' --cli '{{repo-root}}/static/logseq-cli.js' --graph '{{graph}}' --config-a '{{config-path}}' --data-dir-a '{{data-dir}}' --config-b '{{tmp-dir}}/cli-b.edn' --data-dir-b '{{tmp-dir}}/graphs-b' --query '[:find (pull ?b [:block/uuid :block/title :block/order {:block/parent [:block/uuid]}]) :where [?p :block/title \"{{random-page}}\"] [?b :block/page ?p] [?b :block/uuid]]' --require-result" + "{{cli-home}} --data-dir '{{tmp-dir}}/graphs-b' --config '{{tmp-dir}}/cli-b.edn' --output json sync status --graph {{graph-arg}}"], + :id "sync-offline-random-bidirectional-block-ops", + :graph "sync-e2e-offline-random-bidirectional-block-ops", + :vars + {:sync-port "18080", + :seed-marker "sync-offline-random-seed-marker", + :sync-http-base "http://127.0.0.1:18080", + :random-seed "989898", + :random-page "SyncOfflineRandomOpsHome", + :auth-path "{{tmp-dir}}/home/logseq/auth.json", + :home-dir "{{tmp-dir}}/home", + :sync-ws-url "ws://127.0.0.1:18080/sync/%s", + :cli-home "HOME='{{tmp-dir}}/home' {{cli}}", + :rounds-per-client "100"}}]} diff --git a/cli-e2e/src/logseq/cli/e2e/cleanup.clj b/cli-e2e/src/logseq/cli/e2e/cleanup.clj index b0739e0611..a1753fc1f0 100644 --- a/cli-e2e/src/logseq/cli/e2e/cleanup.clj +++ b/cli-e2e/src/logseq/cli/e2e/cleanup.clj @@ -4,6 +4,7 @@ [clojure.string :as string])) (def ^:private cli-e2e-temp-prefix "logseq-cli-e2e-") +(def ^:private db-sync-default-port 18080) (defn- parse-ps-line [line] @@ -82,6 +83,68 @@ :killed-pids killed-pids :failed-pids failed-pids}))))) +(defn- parse-long-safe + [value] + (try + (Long/parseLong value) + (catch Exception _ + nil))) + +(defn list-cli-e2e-db-sync-port-pids + ([] + (list-cli-e2e-db-sync-port-pids {})) + ([{:keys [shell-fn port] + :or {shell-fn java-shell/sh + port db-sync-default-port}}] + (let [{:keys [exit out err]} (shell-fn "lsof" "-nP" (str "-iTCP:" port) "-sTCP:LISTEN") + out-lines (->> (string/split-lines (or out "")) + (map string/trim) + (remove string/blank?) + vec)] + (when (and (not (zero? exit)) + (or (not (string/blank? err)) + (seq out-lines))) + (throw (ex-info "Unable to scan db-sync server port listeners" + {:exit exit + :err err + :port port}))) + (->> out-lines + (filter #(re-find (re-pattern (str ":" port "\\b")) %)) + (keep (fn [line] + (some-> (string/split line #"\s+") + (nth 1 nil) + parse-long-safe))) + distinct + vec)))) + +(defn cleanup-db-sync-port-processes! + ([] + (cleanup-db-sync-port-processes! {})) + ([{:keys [dry-run list-pids-fn kill-pid-fn] + :as _opts}] + (let [list-pids-fn (or list-pids-fn list-cli-e2e-db-sync-port-pids) + kill-pid-fn (or kill-pid-fn kill-pid!) + found-pids (vec (list-pids-fn))] + (if dry-run + {:dry-run? true + :found-pids found-pids + :would-kill-pids found-pids + :killed-pids [] + :failed-pids []} + (let [{:keys [killed-pids failed-pids]} + (reduce (fn [acc pid] + (if (= :failed (kill-pid-fn pid)) + (update acc :failed-pids conj pid) + (update acc :killed-pids conj pid))) + {:killed-pids [] + :failed-pids []} + found-pids)] + {:dry-run? false + :found-pids found-pids + :would-kill-pids [] + :killed-pids killed-pids + :failed-pids failed-pids}))))) + (defn- list-cli-e2e-temp-graph-dirs [tmp-root] (->> (fs/glob tmp-root (str cli-e2e-temp-prefix "*/graphs")) diff --git a/cli-e2e/src/logseq/cli/e2e/main.clj b/cli-e2e/src/logseq/cli/e2e/main.clj index e6875be4b8..6d1f6c69bd 100644 --- a/cli-e2e/src/logseq/cli/e2e/main.clj +++ b/cli-e2e/src/logseq/cli/e2e/main.clj @@ -6,7 +6,8 @@ [logseq.cli.e2e.preflight :as preflight] [logseq.cli.e2e.report :as report] [logseq.cli.e2e.runner :as runner] - [logseq.cli.e2e.shell :as shell])) + [logseq.cli.e2e.shell :as shell] + [logseq.cli.e2e.sync-fixture :as sync-fixture])) (defn select-cases [cases {:keys [case include]}] @@ -38,7 +39,7 @@ (format "%.2fs" (/ (double (- (System/nanoTime) started-at)) 1000000000.0))) (defn- run-selected-cases! - [selected-cases run-case run-command {:keys [on-case-start on-case-success on-case-failure detailed-log?]}] + [selected-cases run-case run-command {:keys [on-case-start on-case-success on-case-failure detailed-log? timings?]}] (let [total (count selected-cases)] (reduce (fn [acc [idx case]] (let [index (inc idx) @@ -49,7 +50,8 @@ :case case})) (try (let [result (run-case case {:run-command run-command - :detailed-log? detailed-log?}) + :detailed-log? detailed-log? + :timings? timings?}) payload {:index index :total total :case case @@ -75,6 +77,7 @@ (let [run-command (or run-command shell/run!) run-case (or (:run-case opts) runner/run-case!) suite (suite-from-opts opts) + sync-suite? (= suite :sync) targeted-run? (or (:case opts) (seq (:include opts))) on-preflight-start (:on-preflight-start opts) on-preflight-complete (:on-preflight-complete opts) @@ -98,10 +101,21 @@ (when on-cases-ready (on-cases-ready {:total (count selected-cases) :targeted-run? targeted-run?})) - {:status :ok - :cases selected-cases - :coverage coverage-result - :results (run-selected-cases! selected-cases run-case run-command opts)})))) + (let [suite-context (when sync-suite? + (sync-fixture/before-suite! {:run-command run-command})) + run-case* (if sync-suite? + (fn [case case-opts] + (run-case (sync-fixture/prepare-case case suite-context) + case-opts)) + run-case)] + (try + {:status :ok + :cases selected-cases + :coverage coverage-result + :results (run-selected-cases! selected-cases run-case* run-command opts)} + (finally + (when suite-context + (sync-fixture/after-suite! suite-context {:run-command run-command}))))))))) (defn build! [opts] (preflight/run! opts)) @@ -125,6 +139,7 @@ (println) (println "Cleanups performed:") (println " - Terminate cli-e2e db-worker-node processes") + (println " - Terminate db-sync server listeners on port 18080") (println " - Remove cli-e2e temp graph directories") (flush)) @@ -138,6 +153,7 @@ cleanup-opts (cond-> {} dry-run? (assoc :dry-run true)) processes (cleanup/cleanup-db-worker-processes! cleanup-opts) + db-sync-port-processes (cleanup/cleanup-db-sync-port-processes! cleanup-opts) temp-graphs (cleanup/cleanup-temp-graph-dirs! cleanup-opts)] (println "==> Running cli-e2e cleanup") (if dry-run? @@ -145,6 +161,9 @@ (println (format "[dry-run] db-worker-node processes: found %d, would kill %d" (count (:found-pids processes)) (count (:would-kill-pids processes)))) + (println (format "[dry-run] db-sync server processes (port 18080): found %d, would kill %d" + (count (:found-pids db-sync-port-processes)) + (count (:would-kill-pids db-sync-port-processes)))) (println (format "[dry-run] temp graph directories: found %d, would remove %d" (count (:found-dirs temp-graphs)) (count (:would-remove-dirs temp-graphs))))) @@ -153,6 +172,10 @@ (count (:found-pids processes)) (count (:killed-pids processes)) (count (:failed-pids processes)))) + (println (format "db-sync server processes (port 18080): found %d, killed %d, failed %d" + (count (:found-pids db-sync-port-processes)) + (count (:killed-pids db-sync-port-processes)) + (count (:failed-pids db-sync-port-processes)))) (println (format "temp graph directories: found %d, removed %d, failed %d" (count (:found-dirs temp-graphs)) (count (:removed-dirs temp-graphs)) @@ -161,6 +184,7 @@ {:status :ok :dry-run? dry-run? :processes processes + :db-sync-port-processes db-sync-port-processes :temp-graphs temp-graphs}))) (defn- print-failure-details! @@ -175,6 +199,38 @@ (println (str " snippet: " snippet))) (flush))) +(defn- format-step-label + [{:keys [phase step-index step-total]}] + (let [phase-name (name (or phase :command))] + (format "%s %d/%d" phase-name (or step-index 1) (or step-total 1)))) + +(defn- print-case-timings! + [timings] + (when (seq timings) + (println " step timings:") + (doseq [{:keys [elapsed-ms status cmd] :as timing} timings] + (println (format " - [%-12s] %6dms (%s) $ %s" + (format-step-label timing) + elapsed-ms + (name (or status :ok)) + cmd))) + (flush))) + +(defn- print-slow-steps! + [all-step-timings] + (when (seq all-step-timings) + (println "Slow steps (top 10):") + (doseq [{:keys [case-id elapsed-ms cmd] :as timing} + (->> all-step-timings + (sort-by :elapsed-ms >) + (take 10))] + (println (format " - %-45s [%-12s] %6dms $ %s" + case-id + (format-step-label timing) + elapsed-ms + cmd))) + (flush))) + (defn- print-test-help! [command-name] (println (str "Usage: bb -f cli-e2e/bb.edn " command-name " [options]")) @@ -185,6 +241,7 @@ (println " -i, --include TAG Run only cases with matching tag (repeatable)") (println " --case ID Run a single case by id") (println " --verbose Enable verbose output") + (println " --timings Print per-step timings and slow-step summary") (println) (println "Examples:") (println (str " bb -f cli-e2e/bb.edn " command-name " --skip-build")) @@ -206,6 +263,8 @@ passed (atom 0) failed (atom 0) total-count (atom 0) + timings? (boolean (:timings opts)) + all-step-timings (atom []) detailed-case-log? (some? (:case opts)) base-run-command (or (:run-command opts) shell/run!) run-command (if detailed-case-log? @@ -222,11 +281,14 @@ (println "==> Running cli-e2e cases") (when detailed-case-log? (println (format "==> Detailed case logging enabled (--case %s)" (:case opts)))) + (when timings? + (println "==> Step timing enabled (--timings)")) (flush) (try (run! (assoc opts :run-command run-command :detailed-log? detailed-case-log? + :timings? timings? :on-preflight-start (fn [_] (println "==> Build preflight: running...") (flush)) @@ -249,6 +311,11 @@ total (:id result) elapsed-ms)) + (when timings? + (let [case-timings (vec (:timings result))] + (swap! all-step-timings into + (map #(assoc % :case-id (:id result)) case-timings)) + (print-case-timings! case-timings))) (flush)) :on-case-failure (fn [{:keys [index total case error elapsed-ms]}] (swap! failed inc) @@ -257,15 +324,24 @@ total (:id case) elapsed-ms)) - (print-failure-details! error)))) + (print-failure-details! error) + (when timings? + (let [case-timings (vec (:timings (ex-data error)))] + (swap! all-step-timings into + (map #(assoc % :case-id (:id case)) case-timings)) + (print-case-timings! case-timings)))))) (println (format "Summary: %d passed, %d failed" @passed @failed)) (println (str "Selected cases: " @total-count)) (println (str "Duration: " (format-duration started-at))) + (when timings? + (print-slow-steps! @all-step-timings)) (catch Exception error (let [failed-count (max 1 @failed)] (println (format "Summary: %d passed, %d failed" @passed failed-count)) (println (str "Selected cases: " (max @total-count failed-count))) - (println (str "Duration: " (format-duration started-at)))) + (println (str "Duration: " (format-duration started-at))) + (when timings? + (print-slow-steps! @all-step-timings))) (throw error))))))) (defn test! diff --git a/cli-e2e/src/logseq/cli/e2e/manifests.clj b/cli-e2e/src/logseq/cli/e2e/manifests.clj index 941e97d19c..456bb284f7 100644 --- a/cli-e2e/src/logseq/cli/e2e/manifests.clj +++ b/cli-e2e/src/logseq/cli/e2e/manifests.clj @@ -10,6 +10,12 @@ (def default-suite :non-sync) +(def ^:private append-merge-keys + #{:setup :cmds :cleanup :tags}) + +(def ^:private deep-merge-keys + #{:vars :covers :expect}) + (defn read-edn-file [path] (edn/read-string (slurp path))) @@ -37,8 +43,114 @@ ([suite] (read-edn-file (paths/spec-path (manifest-file suite :inventory))))) +(defn- normalize-extends + [extends] + (cond + (nil? extends) [] + (keyword? extends) [extends] + (vector? extends) extends + :else + (throw (ex-info "Invalid :extends value in cli-e2e manifest" + {:extends extends + :expected "keyword | vector | nil"})))) + +(defn- as-seq + [value] + (cond + (nil? value) [] + (sequential? value) value + :else [value])) + +(defn- deep-merge-maps + [left right] + (merge-with (fn [left-val right-val] + (if (and (map? left-val) + (map? right-val)) + (deep-merge-maps left-val right-val) + right-val)) + (or left {}) + (or right {}))) + +(defn- merge-entry + [parent child] + (let [all-keys (set (concat (keys parent) (keys child)))] + (reduce (fn [acc key] + (let [parent-val (get parent key) + child-val (get child key)] + (assoc acc + key + (cond + (contains? append-merge-keys key) + (if (contains? child key) + (vec (concat (as-seq parent-val) + (as-seq child-val))) + (vec (as-seq parent-val))) + + (contains? deep-merge-keys key) + (if (contains? child key) + (deep-merge-maps parent-val child-val) + parent-val) + + (contains? child key) + child-val + + :else + parent-val)))) + {} + all-keys))) + +(defn- resolve-template + [templates template-id stack] + (when (some #{template-id} stack) + (throw (ex-info "Circular template inheritance detected in cli-e2e manifest" + {:template template-id + :cycle (conj (vec stack) template-id)}))) + (let [template (get templates template-id)] + (when-not template + (throw (ex-info "Unknown template in cli-e2e manifest" + {:template template-id + :known-templates (sort (keys templates))}))) + (let [parent-ids (normalize-extends (:extends template)) + parent-values (map #(resolve-template templates % (conj stack template-id)) + parent-ids)] + (reduce merge-entry + {} + (concat parent-values + [(dissoc template :extends)]))))) + +(defn- expand-manifest-cases + [manifest-data] + (cond + (vector? manifest-data) + (vec manifest-data) + + (map? manifest-data) + (let [templates (:templates manifest-data {}) + cases (:cases manifest-data)] + (when-not (vector? cases) + (throw (ex-info "Invalid cli-e2e manifest format" + {:manifest-type :map + :expected "{:templates {...} :cases [...] }"}))) + (mapv (fn [case] + (let [parent-ids (normalize-extends (:extends case)) + parent-values (map #(resolve-template templates % []) + parent-ids)] + (reduce merge-entry + {} + (concat parent-values + [(dissoc case :extends)])))) + cases)) + + :else + (throw (ex-info "Invalid cli-e2e manifest format" + {:manifest-type (type manifest-data) + :expected "vector | {:templates ... :cases ...}"})))) + (defn load-cases ([] (load-cases nil)) ([suite] - (read-edn-file (paths/spec-path (manifest-file suite :cases))))) + (-> (manifest-file suite :cases) + paths/spec-path + read-edn-file + expand-manifest-cases))) diff --git a/cli-e2e/src/logseq/cli/e2e/runner.clj b/cli-e2e/src/logseq/cli/e2e/runner.clj index cd3eb40e01..ff69fc133a 100644 --- a/cli-e2e/src/logseq/cli/e2e/runner.clj +++ b/cli-e2e/src/logseq/cli/e2e/runner.clj @@ -172,34 +172,67 @@ :case-id case-id :throw? (not allow-failure)})) +(defn- elapsed-ms + [started-at] + (long (/ (- (System/nanoTime) started-at) 1000000))) + +(defn- run-step! + [timings command context {:keys [timings? phase step-index step-total] + :as run-opts}] + (if-not timings? + (run-command! command context run-opts) + (let [started-at (System/nanoTime)] + (try + (let [result (run-command! command context run-opts)] + (swap! timings conj {:phase phase + :step-index step-index + :step-total step-total + :cmd (:cmd result) + :elapsed-ms (elapsed-ms started-at) + :status :ok}) + result) + (catch Exception error + (swap! timings conj {:phase phase + :step-index step-index + :step-total step-total + :cmd (or (:cmd (ex-data error)) + (render-string command context)) + :elapsed-ms (elapsed-ms started-at) + :status :failed}) + (throw error)))))) + (defn run-case! - [case {:keys [run-command detailed-log?] + [case {:keys [run-command detailed-log? timings?] :or {run-command shell/run!} :as opts}] (let [context (case-context case opts) rendered (render-case case context) case-id (:id rendered) + timings? (boolean timings?) + timings (atom []) cleanup-commands (vec (:cleanup rendered)) setup-commands (vec (:setup rendered)) main-commands (vec (:cmds rendered)) cleanup! (fn [] (doseq [[idx command] (map-indexed vector cleanup-commands)] (try - (run-command! command context {:run-command run-command - :allow-failure true - :phase (when detailed-log? :cleanup) - :step-index (inc idx) - :step-total (count cleanup-commands) - :case-id case-id}) + (run-step! timings command context {:run-command run-command + :timings? timings? + :allow-failure true + :phase (when (or detailed-log? timings?) :cleanup) + :step-index (inc idx) + :step-total (count cleanup-commands) + :case-id case-id}) (catch Exception _ nil))))] - (doseq [[idx command] (map-indexed vector setup-commands)] - (run-command! command context {:run-command run-command - :phase (when detailed-log? :setup) - :step-index (inc idx) - :step-total (count setup-commands) - :case-id case-id})) (try + (doseq [[idx command] (map-indexed vector setup-commands)] + (run-step! timings command context {:run-command run-command + :timings? timings? + :phase (when (or detailed-log? timings?) :setup) + :step-index (inc idx) + :step-total (count setup-commands) + :case-id case-id})) (let [main-total (count main-commands) _ (when (zero? main-total) (throw (ex-info "Missing case commands" @@ -207,20 +240,30 @@ :case rendered}))) result (reduce (fn [_ [idx command]] (let [last-step? (= idx (dec main-total))] - (run-command! command context {:run-command run-command - :stdin (when last-step? (:stdin rendered)) - :allow-failure last-step? - :phase (when detailed-log? :main) - :step-index (inc idx) - :step-total main-total - :case-id case-id}))) + (run-step! timings command context {:run-command run-command + :timings? timings? + :stdin (when last-step? (:stdin rendered)) + :allow-failure last-step? + :phase (when (or detailed-log? timings?) :main) + :step-index (inc idx) + :step-total main-total + :case-id case-id}))) nil (map-indexed vector main-commands))] (assert-result! rendered result) - {:id case-id - :status :ok - :cmd (:cmd result) - :result result - :context context}) - (finally - (cleanup!))))) + (cleanup!) + (cond-> {:id case-id + :status :ok + :cmd (:cmd result) + :result result + :context context} + timings? (assoc :timings @timings))) + (catch Exception error + (cleanup!) + (if timings? + (throw (ex-info (.getMessage error) + (assoc (or (ex-data error) {}) + :timings @timings + :case-id case-id) + error)) + (throw error)))))) diff --git a/cli-e2e/src/logseq/cli/e2e/sync_fixture.clj b/cli-e2e/src/logseq/cli/e2e/sync_fixture.clj new file mode 100644 index 0000000000..d4339282e5 --- /dev/null +++ b/cli-e2e/src/logseq/cli/e2e/sync_fixture.clj @@ -0,0 +1,104 @@ +(ns logseq.cli.e2e.sync-fixture + (:require [babashka.fs :as fs] + [clojure.string :as string] + [logseq.cli.e2e.paths :as paths] + [logseq.cli.e2e.runner :as runner] + [logseq.cli.e2e.shell :as shell])) + +(def default-sync-port "18080") + +(def ^:private heavy-setup-patterns + [#"^mkdir -p '\{\{tmp-dir\}\}/home/logseq'$" + #"^cp ~/logseq/auth\.json\b" + #"prepare_sync_config\.py" + #"db_sync_server\.py'? start"]) + +(def ^:private heavy-cleanup-patterns + [#"db_sync_server\.py'? stop"]) + +(defn- shell-quote + [value] + (runner/shell-escape value)) + +(defn- heavy-command? + [command patterns] + (boolean (some #(re-find % command) patterns))) + +(defn before-suite! + [{:keys [run-command sync-port] + :or {run-command shell/run! + sync-port default-sync-port}}] + (let [sync-port (str sync-port) + suite-tmp-dir (str (fs/create-temp-dir {:prefix "logseq-cli-e2e-sync-suite-"})) + suite-home-dir (str (fs/path suite-tmp-dir "home")) + suite-auth-path (str (fs/path suite-home-dir "logseq" "auth.json")) + suite-config-path (str (fs/path suite-tmp-dir "sync-suite.edn")) + db-sync-pid-file (str (fs/path suite-tmp-dir "db-sync-server.pid")) + db-sync-log-file (str (fs/path suite-tmp-dir "db-sync-server.log")) + db-sync-data-dir (str (fs/path suite-tmp-dir "db-sync-server-data")) + sync-http-base (str "http://127.0.0.1:" sync-port) + sync-ws-url (str "ws://127.0.0.1:" sync-port "/sync/%s") + run! (fn [cmd] + (run-command {:cmd cmd + :dir (paths/repo-root)})) + prepare-sync-config-cmd (format "python3 %s --output %s --auth-path %s --http-base %s --ws-url %s" + (shell-quote (paths/repo-path "cli-e2e" "scripts" "prepare_sync_config.py")) + (shell-quote suite-config-path) + (shell-quote suite-auth-path) + (shell-quote sync-http-base) + (shell-quote sync-ws-url)) + start-db-sync-cmd (format "python3 %s start --repo-root %s --pid-file %s --log-file %s --data-dir %s --port %s --startup-timeout-s 60 --auth-path %s" + (shell-quote (paths/repo-path "cli-e2e" "scripts" "db_sync_server.py")) + (shell-quote (paths/repo-root)) + (shell-quote db-sync-pid-file) + (shell-quote db-sync-log-file) + (shell-quote db-sync-data-dir) + sync-port + (shell-quote suite-auth-path))] + (fs/create-dirs (fs/parent suite-auth-path)) + (run! (format "cp ~/logseq/auth.json %s" (shell-quote suite-auth-path))) + (run! prepare-sync-config-cmd) + (run! start-db-sync-cmd) + {:suite-tmp-dir suite-tmp-dir + :suite-home-dir suite-home-dir + :suite-auth-path suite-auth-path + :suite-config-path suite-config-path + :db-sync-pid-file db-sync-pid-file + :db-sync-log-file db-sync-log-file + :db-sync-data-dir db-sync-data-dir + :sync-port sync-port + :sync-http-base sync-http-base + :sync-ws-url sync-ws-url})) + +(defn prepare-case + [case {:keys [suite-auth-path suite-config-path sync-port sync-http-base sync-ws-url]}] + (let [lightweight-setup-prefix ["mkdir -p '{{tmp-dir}}/home/logseq'" + "cp '{{suite-auth-path}}' '{{tmp-dir}}/home/logseq/auth.json'" + "cp '{{suite-config-path}}' '{{config-path}}'" + "cp '{{suite-config-path}}' '{{tmp-dir}}/cli-b.edn'"] + setup' (->> (:setup case) + (remove #(heavy-command? % heavy-setup-patterns)) + vec) + cleanup' (->> (:cleanup case) + (remove #(heavy-command? % heavy-cleanup-patterns)) + vec)] + (-> case + (update :vars merge {:suite-auth-path suite-auth-path + :suite-config-path suite-config-path + :sync-port sync-port + :sync-http-base sync-http-base + :sync-ws-url sync-ws-url}) + (assoc :setup (vec (concat lightweight-setup-prefix setup'))) + (assoc :cleanup cleanup')))) + +(defn after-suite! + [{:keys [db-sync-pid-file]} + {:keys [run-command] + :or {run-command shell/run!}}] + (when (and (string? db-sync-pid-file) + (not (string/blank? db-sync-pid-file))) + (run-command {:cmd (format "python3 %s stop --pid-file %s" + (shell-quote (paths/repo-path "cli-e2e" "scripts" "db_sync_server.py")) + (shell-quote db-sync-pid-file)) + :dir (paths/repo-root) + :throw? false}))) diff --git a/cli-e2e/src/logseq/cli/e2e/test_runner.clj b/cli-e2e/src/logseq/cli/e2e/test_runner.clj index 2efb893625..1ad6f2a50d 100644 --- a/cli-e2e/src/logseq/cli/e2e/test_runner.clj +++ b/cli-e2e/src/logseq/cli/e2e/test_runner.clj @@ -7,6 +7,8 @@ logseq.cli.e2e.shell-test logseq.cli.e2e.runner-test logseq.cli.e2e.cleanup-test + logseq.cli.e2e.manifests-test + logseq.cli.e2e.sync-fixture-test logseq.cli.e2e.main-test]) (defn run! diff --git a/cli-e2e/test/logseq/cli/e2e/cleanup_test.clj b/cli-e2e/test/logseq/cli/e2e/cleanup_test.clj index e05cc8b4cc..c3fba2ed45 100644 --- a/cli-e2e/test/logseq/cli/e2e/cleanup_test.clj +++ b/cli-e2e/test/logseq/cli/e2e/cleanup_test.clj @@ -51,6 +51,60 @@ (is (= [] (:failed-pids result))) (is (= [] @killed)))) +(deftest list-cli-e2e-db-sync-port-pids-filters-port-18080 + (let [shell-fn (fn [& _] + {:exit 0 + :out (str "COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\n" + "node 111 me 13u IPv6 0x0 0t0 TCP *:18080 (LISTEN)\n" + "python 222 me 13u IPv4 0x0 0t0 TCP 127.0.0.1:18080 (LISTEN)\n" + "node 333 me 13u IPv6 0x0 0t0 TCP *:18081 (LISTEN)\n") + :err ""})] + (is (= [111 222] + (cleanup/list-cli-e2e-db-sync-port-pids {:shell-fn shell-fn})))) + + (testing "returns empty when no listener exists" + (is (= [] + (cleanup/list-cli-e2e-db-sync-port-pids {:shell-fn (fn [& _] + {:exit 1 + :out "" + :err ""})})))) + + (testing "throws when lsof invocation fails" + (is (thrown-with-msg? + clojure.lang.ExceptionInfo + #"Unable to scan db-sync server port listeners" + (cleanup/list-cli-e2e-db-sync-port-pids {:shell-fn (fn [& _] + {:exit 1 + :out "" + :err "permission denied"})}))))) + +(deftest cleanup-db-sync-port-processes-separates-killed-and-failed + (let [killed (atom []) + result (cleanup/cleanup-db-sync-port-processes! {:list-pids-fn (fn [] [44 55]) + :kill-pid-fn (fn [pid] + (swap! killed conj pid) + (if (= pid 55) + :failed + :killed))})] + (is (= [44 55] (:found-pids result))) + (is (= [44] (:killed-pids result))) + (is (= [55] (:failed-pids result))) + (is (= [44 55] @killed)))) + +(deftest cleanup-db-sync-port-processes-dry-run-does-not-kill + (let [killed (atom []) + result (cleanup/cleanup-db-sync-port-processes! {:dry-run true + :list-pids-fn (fn [] [44]) + :kill-pid-fn (fn [pid] + (swap! killed conj pid) + :killed)})] + (is (= [44] (:found-pids result))) + (is (true? (:dry-run? result))) + (is (= [44] (:would-kill-pids result))) + (is (= [] (:killed-pids result))) + (is (= [] (:failed-pids result))) + (is (= [] @killed)))) + (deftest cleanup-temp-graph-dirs-removes-only-cli-e2e-graphs (let [tmp-root (fs/create-temp-dir {:prefix "cleanup-e2e-test-"}) matching-graphs (fs/path tmp-root "logseq-cli-e2e-case-a-123" "graphs") diff --git a/cli-e2e/test/logseq/cli/e2e/main_test.clj b/cli-e2e/test/logseq/cli/e2e/main_test.clj index 6e2f729570..298a35d3ed 100644 --- a/cli-e2e/test/logseq/cli/e2e/main_test.clj +++ b/cli-e2e/test/logseq/cli/e2e/main_test.clj @@ -3,7 +3,8 @@ [clojure.test :refer [deftest is testing]] [logseq.cli.e2e.cleanup :as cleanup] [logseq.cli.e2e.main :as main] - [logseq.cli.e2e.manifests :as manifests])) + [logseq.cli.e2e.manifests :as manifests] + [logseq.cli.e2e.sync-fixture :as sync-fixture])) (def sample-cases [{:id "global-help" @@ -168,6 +169,48 @@ [:cases :sync]] @suite-calls))))) +(deftest run-sync-suite-uses-suite-fixture-once + (let [before-called (atom 0) + after-called (atom 0) + prepared-case-ids (atom []) + run-case-seen (atom []) + sync-inventory {:excluded-command-prefixes ["login" "logout"] + :scopes {:sync {:commands ["sync upload" "sync status"] + :options []}}} + sync-cases [{:id "sync-upload" + :cmds ["node static/logseq-cli.js sync upload"] + :covers {:commands ["sync upload"]}} + {:id "sync-status" + :cmds ["node static/logseq-cli.js sync status"] + :covers {:commands ["sync status"]}}]] + (with-redefs [sync-fixture/before-suite! (fn [_] + (swap! before-called inc) + {:suite :sync}) + sync-fixture/prepare-case (fn [case _suite-context] + (swap! prepared-case-ids conj (:id case)) + (assoc case :prepared? true)) + sync-fixture/after-suite! (fn [_ _] + (swap! after-called inc))] + (let [result (main/run! {:suite :sync + :inventory sync-inventory + :cases sync-cases + :skip-build true + :run-command (fn [_] + {:exit 0 + :out "" + :err ""}) + :run-case (fn [case _opts] + (swap! run-case-seen conj [(:id case) (:prepared? case)]) + {:id (:id case) + :status :ok})})] + (is (= :ok (:status result))))) + (is (= 1 @before-called)) + (is (= 1 @after-called)) + (is (= ["sync-upload" "sync-status"] @prepared-case-ids)) + (is (= [["sync-upload" true] + ["sync-status" true]] + @run-case-seen)))) + (deftest list-cases-defaults-to-non-sync (let [selected-suite (atom nil) output (with-out-str @@ -212,6 +255,46 @@ (is (string/includes? output "[2/2] ✓ graph-list")) (is (string/includes? output "Summary: 2 passed, 0 failed")))) +(deftest test-timings-prints-step-details-and-slow-summary + (let [output (with-out-str + (main/test! {:inventory complete-inventory + :cases sample-cases + :include ["smoke"] + :skip-build true + :timings true + :run-command (fn [_] + {:exit 0 + :out "" + :err ""}) + :run-case (fn [case _opts] + (if (= "global-help" (:id case)) + {:id "global-help" + :status :ok + :timings [{:phase :setup + :step-index 1 + :step-total 1 + :elapsed-ms 12 + :status :ok + :cmd "setup-global"} + {:phase :main + :step-index 1 + :step-total 1 + :elapsed-ms 55 + :status :ok + :cmd "main-global"}]} + {:id "graph-list" + :status :ok + :timings [{:phase :main + :step-index 1 + :step-total 1 + :elapsed-ms 210 + :status :ok + :cmd "main-graph-list"}]}))}))] + (is (string/includes? output "==> Step timing enabled (--timings)")) + (is (string/includes? output "step timings:")) + (is (string/includes? output "Slow steps (top 10):")) + (is (string/includes? output "main-graph-list")))) + (deftest test-help-prints-usage-and-skips-execution (let [ran? (atom false) result (atom nil) @@ -232,7 +315,8 @@ (is (string/includes? output "Usage: bb -f cli-e2e/bb.edn test [options]")) (is (string/includes? output "--skip-build")) (is (string/includes? output "--include TAG")) - (is (string/includes? output "--case ID")))) + (is (string/includes? output "--case ID")) + (is (string/includes? output "--timings")))) (deftest test-sync-help-prints-usage-and-skips-execution (let [ran? (atom false) @@ -254,7 +338,8 @@ (is (string/includes? output "Usage: bb -f cli-e2e/bb.edn test-sync [options]")) (is (string/includes? output "--skip-build")) (is (string/includes? output "--include TAG")) - (is (string/includes? output "--case ID")))) + (is (string/includes? output "--case ID")) + (is (string/includes? output "--timings")))) (deftest test-single-case-enables-detailed-command-logging (let [command-opts (atom nil) @@ -287,12 +372,16 @@ (let [output (with-out-str (main/cleanup! {:help true}))] (is (string/includes? output "Usage: bb -f cli-e2e/bb.edn cleanup")) (is (string/includes? output "Terminate cli-e2e db-worker-node processes")) + (is (string/includes? output "Terminate db-sync server listeners on port 18080")) (is (string/includes? output "--dry-run")))) (deftest cleanup-prints-summary-and-returns-status (with-redefs [cleanup/cleanup-db-worker-processes! (fn [_] {:found-pids [101 202] :killed-pids [101] :failed-pids [202]}) + cleanup/cleanup-db-sync-port-processes! (fn [_] {:found-pids [303] + :killed-pids [303] + :failed-pids []}) cleanup/cleanup-temp-graph-dirs! (fn [_] {:found-dirs ["/tmp/logseq-cli-e2e-a/graphs" "/tmp/logseq-cli-e2e-b/graphs"] :removed-dirs ["/tmp/logseq-cli-e2e-a/graphs"] @@ -302,12 +391,15 @@ (reset! result (main/cleanup! {})))] (is (= :ok (:status @result))) (is (= [101] (get-in @result [:processes :killed-pids]))) + (is (= [303] (get-in @result [:db-sync-port-processes :killed-pids]))) (is (= ["/tmp/logseq-cli-e2e-a/graphs"] (get-in @result [:temp-graphs :removed-dirs]))) (is (string/includes? output "db-worker-node processes: found 2, killed 1, failed 1")) + (is (string/includes? output "db-sync server processes (port 18080): found 1, killed 1, failed 0")) (is (string/includes? output "temp graph directories: found 2, removed 1, failed 1"))))) (deftest cleanup-dry-run-prints-summary-and-passes-option (let [process-opts (atom nil) + db-sync-opts (atom nil) dir-opts (atom nil)] (with-redefs [cleanup/cleanup-db-worker-processes! (fn [opts] (reset! process-opts opts) @@ -316,6 +408,13 @@ :would-kill-pids [101 202] :killed-pids [] :failed-pids []}) + cleanup/cleanup-db-sync-port-processes! (fn [opts] + (reset! db-sync-opts opts) + {:dry-run? true + :found-pids [303] + :would-kill-pids [303] + :killed-pids [] + :failed-pids []}) cleanup/cleanup-temp-graph-dirs! (fn [opts] (reset! dir-opts opts) {:dry-run? true @@ -327,9 +426,12 @@ output (with-out-str (reset! result (main/cleanup! {:dry-run true})))] (is (= {:dry-run true} @process-opts)) + (is (= {:dry-run true} @db-sync-opts)) (is (= {:dry-run true} @dir-opts)) (is (= :ok (:status @result))) (is (true? (get-in @result [:processes :dry-run?]))) + (is (true? (get-in @result [:db-sync-port-processes :dry-run?]))) (is (true? (get-in @result [:temp-graphs :dry-run?]))) (is (string/includes? output "[dry-run] db-worker-node processes: found 2, would kill 2")) + (is (string/includes? output "[dry-run] db-sync server processes (port 18080): found 1, would kill 1")) (is (string/includes? output "[dry-run] temp graph directories: found 1, would remove 1")))))) diff --git a/cli-e2e/test/logseq/cli/e2e/manifests_test.clj b/cli-e2e/test/logseq/cli/e2e/manifests_test.clj new file mode 100644 index 0000000000..4c2d5d3291 --- /dev/null +++ b/cli-e2e/test/logseq/cli/e2e/manifests_test.clj @@ -0,0 +1,78 @@ +(ns logseq.cli.e2e.manifests-test + (:require [clojure.test :refer [deftest is testing]] + [logseq.cli.e2e.manifests :as manifests])) + +(deftest load-cases-supports-legacy-vector-format + (with-redefs [manifests/read-edn-file (fn [_] + [{:id "legacy-a"} + {:id "legacy-b"}])] + (is (= ["legacy-a" "legacy-b"] + (mapv :id (manifests/load-cases :non-sync)))))) + +(deftest load-cases-supports-templates-and-inheritance + (with-redefs [manifests/read-edn-file (fn [_] + {:templates + {:base {:setup ["setup-a"] + :cmds ["cmd-a"] + :cleanup ["cleanup-a"] + :tags [:base] + :vars {:nested {:left 1} + :only-base true} + :covers {:commands ["base-command"] + :options {:global ["--base"]}} + :expect {:stdout-json-paths {[:status] "ok"}} + :graph "base-graph"} + :addon {:setup ["setup-b"] + :cmds ["cmd-b"] + :cleanup ["cleanup-b"] + :tags [:addon] + :vars {:nested {:right 2}} + :covers {:options {:graph ["--addon"]}} + :expect {:stdout-json-paths {[:data :x] 1}} + :graph "addon-graph"}} + :cases + [{:id "templated" + :extends [:base :addon] + :setup ["setup-case"] + :cmds ["cmd-case"] + :cleanup ["cleanup-case"] + :tags [:case] + :vars {:nested {:leaf 3} + :only-case true} + :covers {:options {:graph ["--case"]}} + :expect {:stdout-json-paths {[:data :y] 2}} + :graph "case-graph"}]})] + (let [case (first (manifests/load-cases :sync))] + (testing "append merge keys" + (is (= ["setup-a" "setup-b" "setup-case"] (:setup case))) + (is (= ["cmd-a" "cmd-b" "cmd-case"] (:cmds case))) + (is (= ["cleanup-a" "cleanup-b" "cleanup-case"] (:cleanup case))) + (is (= [:base :addon :case] (:tags case)))) + (testing "deep merge keys" + (is (= {:nested {:left 1 :right 2 :leaf 3} + :only-base true + :only-case true} + (:vars case))) + (is (= {:commands ["base-command"] + :options {:global ["--base"] + :graph ["--case"]}} + (:covers case))) + (is (= {[:status] "ok" + [:data :x] 1 + [:data :y] 2} + (get-in case [:expect :stdout-json-paths])))) + (testing "scalar child override" + (is (= "case-graph" (:graph case))))))) + +(deftest load-cases-detects-circular-template-inheritance + (with-redefs [manifests/read-edn-file (fn [_] + {:templates + {:a {:extends :b + :setup ["a"]} + :b {:extends :a + :setup ["b"]}} + :cases [{:id "cycle" :extends :a}]})] + (is (thrown-with-msg? + clojure.lang.ExceptionInfo + #"Circular template inheritance" + (manifests/load-cases :sync))))) diff --git a/cli-e2e/test/logseq/cli/e2e/runner_test.clj b/cli-e2e/test/logseq/cli/e2e/runner_test.clj index f0f0b95d35..fc6c8b61ea 100644 --- a/cli-e2e/test/logseq/cli/e2e/runner_test.clj +++ b/cli-e2e/test/logseq/cli/e2e/runner_test.clj @@ -57,6 +57,50 @@ {:cmd "cleanup one" :phase :cleanup :step-index 1 :step-total 1 :case-id "graph-info" :throw? false}] @calls)))) +(deftest run-case-collects-step-timings-when-enabled + (let [result (runner/run-case! + {:id "graph-info" + :setup ["setup one"] + :cmds ["main command"] + :cleanup ["cleanup one"] + :expect {:exit 0}} + {:context {} + :timings? true + :run-command (fn [{:keys [cmd]}] + {:cmd cmd + :exit 0 + :out "" + :err ""})})] + (is (= 3 (count (:timings result)))) + (is (= [:setup :main :cleanup] + (mapv :phase (:timings result)))))) + +(deftest run-case-attaches-timings-to-error-when-enabled + (let [error (try + (runner/run-case! + {:id "graph-info" + :setup ["setup one"] + :cmds ["main command"] + :cleanup ["cleanup one"] + :expect {:exit 0}} + {:context {} + :timings? true + :run-command (fn [{:keys [cmd]}] + (when (= cmd "main command") + (throw (ex-info "boom" {:cmd cmd}))) + {:cmd cmd + :exit 0 + :out "" + :err ""})}) + nil + (catch clojure.lang.ExceptionInfo error + error)) + timings (:timings (ex-data error))] + (is (= "graph-info" (:case-id (ex-data error)))) + (is (= [:setup :main :cleanup] + (mapv :phase timings))) + (is (= :failed (:status (second timings)))))) + (deftest run-case-validates-json-paths-and-nonzero-exit (let [result (runner/run-case! {:id "invalid-shell" diff --git a/cli-e2e/test/logseq/cli/e2e/sync_fixture_test.clj b/cli-e2e/test/logseq/cli/e2e/sync_fixture_test.clj new file mode 100644 index 0000000000..3a512035d4 --- /dev/null +++ b/cli-e2e/test/logseq/cli/e2e/sync_fixture_test.clj @@ -0,0 +1,52 @@ +(ns logseq.cli.e2e.sync-fixture-test + (:require [clojure.string :as string] + [clojure.test :refer [deftest is]] + [logseq.cli.e2e.sync-fixture :as sync-fixture])) + +(deftest prepare-case-removes-heavy-steps-and-injects-lightweight-ones + (let [suite-context {:suite-auth-path "/tmp/suite/auth.json" + :suite-config-path "/tmp/suite/config.edn" + :sync-port "18080" + :sync-http-base "http://127.0.0.1:18080" + :sync-ws-url "ws://127.0.0.1:18080/sync/%s"} + input-case {:id "sync-case" + :vars {:existing true} + :setup ["mkdir -p '{{tmp-dir}}/home/logseq'" + "cp ~/logseq/auth.json '{{tmp-dir}}/home/logseq/auth.json'" + "python3 '{{repo-root}}/cli-e2e/scripts/prepare_sync_config.py' --output '{{config-path}}'" + "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' start --port 18080" + "{{cli-home}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json graph create --graph {{graph-arg}} >/dev/null"] + :cleanup ["{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}" + "python3 '{{repo-root}}/cli-e2e/scripts/db_sync_server.py' stop --pid-file '{{tmp-dir}}/db-sync-server.pid'"]} + prepared (sync-fixture/prepare-case input-case suite-context)] + (is (= "sync-case" (:id prepared))) + (is (= 5 (count (:setup prepared)))) + (is (= "mkdir -p '{{tmp-dir}}/home/logseq'" (first (:setup prepared)))) + (is (string/includes? (second (:setup prepared)) "suite-auth-path")) + (is (not-any? #(string/includes? % "prepare_sync_config.py") (:setup prepared))) + (is (not-any? #(string/includes? % "db_sync_server.py' start") (:setup prepared))) + (is (= ["{{cli}} --data-dir {{data-dir-arg}} --config {{config-path-arg}} --output json server stop --graph {{graph-arg}}"] + (:cleanup prepared))) + (is (= "/tmp/suite/auth.json" (get-in prepared [:vars :suite-auth-path]))) + (is (= "http://127.0.0.1:18080" (get-in prepared [:vars :sync-http-base]))))) + +(deftest before-and-after-suite-run-expected-commands + (let [calls (atom []) + run-command (fn [opts] + (swap! calls conj opts) + {:exit 0 + :out "" + :err ""}) + suite-context (sync-fixture/before-suite! {:run-command run-command})] + (is (= 3 (count @calls))) + (is (string/includes? (:cmd (first @calls)) "cp ~/logseq/auth.json")) + (is (string/includes? (:cmd (second @calls)) "prepare_sync_config.py")) + (is (string/includes? (:cmd (nth @calls 2)) "db_sync_server.py")) + (is (string/includes? (:cmd (nth @calls 2)) " start ")) + (is (string/includes? (:cmd (nth @calls 2)) "--port 18080")) + (is (string/includes? (:cmd (nth @calls 2)) "--startup-timeout-s 60")) + (sync-fixture/after-suite! suite-context {:run-command run-command}) + (is (= 4 (count @calls))) + (is (string/includes? (:cmd (last @calls)) "db_sync_server.py")) + (is (string/includes? (:cmd (last @calls)) " stop ")) + (is (false? (:throw? (last @calls))))))