mirror of
https://github.com/logseq/logseq.git
synced 2026-06-01 19:01:22 +00:00
enhance(cli-e2e): add sync suite fixtures, timings, and port cleanup
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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"))))))
|
||||
|
||||
78
cli-e2e/test/logseq/cli/e2e/manifests_test.clj
Normal file
78
cli-e2e/test/logseq/cli/e2e/manifests_test.clj
Normal file
@@ -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)))))
|
||||
@@ -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"
|
||||
|
||||
52
cli-e2e/test/logseq/cli/e2e/sync_fixture_test.clj
Normal file
52
cli-e2e/test/logseq/cli/e2e/sync_fixture_test.clj
Normal file
@@ -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))))))
|
||||
Reference in New Issue
Block a user