enhance(cli-e2e): add bb cleanup

This commit is contained in:
rcmerci
2026-03-22 17:49:45 +08:00
parent 1010341f56
commit d88ae59990
7 changed files with 325 additions and 2 deletions

View File

@@ -10,4 +10,6 @@ Shell-first end-to-end tests for logseq CLI.
- Run cli-e2e cases with build preflight unless --skip-build is provided: `bb test`
- `bb test --help` for more help info
## Clean up
- Execute cleanup: terminate stale db-worker-node processes and remove cli-e2e temp graph directories: `bb cleanup`
- Preview cleanup actions only (no kill/delete): `bb cleanup --dry-run`

View File

@@ -11,6 +11,7 @@
:h :help}
:coerce {:include []
:help :boolean
:dry-run :boolean
:skip-build :boolean
:verbose :boolean}}))
@@ -28,4 +29,8 @@
test
{:doc "Run cli-e2e cases with build preflight unless --skip-build is provided"
:task (main/test! cli-opts)}}}
:task (main/test! cli-opts)}
cleanup
{:doc "Terminate cli-e2e db-worker-node processes and remove temp graph dirs"
:task (main/cleanup! cli-opts)}}}

View File

@@ -0,0 +1,120 @@
(ns logseq.cli.e2e.cleanup
(:require [babashka.fs :as fs]
[clojure.java.shell :as java-shell]
[clojure.string :as string]))
(def ^:private cli-e2e-temp-prefix "logseq-cli-e2e-")
(defn- parse-ps-line
[line]
(when-let [[_ pid-str command] (re-matches #"\s*(\d+)\s+(.*)" (or line ""))]
{:pid (Long/parseLong pid-str)
:command command}))
(defn- cli-e2e-db-worker-command?
[command]
(and (re-find #"db-worker-node(?:\.js)?\b" command)
(re-find #"logseq-cli-e2e-" command)))
(defn list-cli-e2e-db-worker-pids
([]
(list-cli-e2e-db-worker-pids {}))
([{:keys [shell-fn]
:or {shell-fn java-shell/sh}}]
(let [{:keys [exit out err]} (shell-fn "ps" "-ax" "-o" "pid=" "-o" "command=")]
(when-not (zero? exit)
(throw (ex-info "Unable to scan processes"
{:exit exit
:err err})))
(->> (string/split-lines (or out ""))
(keep parse-ps-line)
(filter (fn [{:keys [command]}]
(cli-e2e-db-worker-command? command)))
(mapv :pid)))))
(defn- pid-alive?
[pid]
(zero? (:exit (java-shell/sh "kill" "-0" (str pid)))))
(defn- kill-pid!
[pid]
(try
(if-not (pid-alive? pid)
:not-found
(do
(java-shell/sh "kill" "-TERM" (str pid))
(Thread/sleep 100)
(if-not (pid-alive? pid)
:killed
(do
(java-shell/sh "kill" "-KILL" (str pid))
(Thread/sleep 50)
(if (pid-alive? pid)
:failed
:killed)))))
(catch Exception _
:failed)))
(defn cleanup-db-worker-processes!
([]
(cleanup-db-worker-processes! {}))
([{:keys [dry-run list-pids-fn kill-pid-fn]}]
(let [list-pids-fn (or list-pids-fn list-cli-e2e-db-worker-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"))
(filter fs/directory?)
(mapv str)))
(defn cleanup-temp-graph-dirs!
([]
(cleanup-temp-graph-dirs! {}))
([{:keys [dry-run tmp-root list-dirs-fn delete-dir-fn]
:or {tmp-root (System/getProperty "java.io.tmpdir")
delete-dir-fn fs/delete-tree}}]
(let [list-dirs-fn (or list-dirs-fn
#(list-cli-e2e-temp-graph-dirs tmp-root))
found-dirs (vec (list-dirs-fn))]
(if dry-run
{:dry-run? true
:found-dirs found-dirs
:would-remove-dirs found-dirs
:removed-dirs []
:failed-dirs []}
(let [{:keys [removed-dirs failed-dirs]}
(reduce (fn [acc dir]
(try
(delete-dir-fn dir)
(update acc :removed-dirs conj dir)
(catch Exception _
(update acc :failed-dirs conj dir))))
{:removed-dirs []
:failed-dirs []}
found-dirs)]
{:dry-run? false
:found-dirs found-dirs
:would-remove-dirs []
:removed-dirs removed-dirs
:failed-dirs failed-dirs})))))

View File

@@ -1,5 +1,6 @@
(ns logseq.cli.e2e.main
(:require [clojure.set :as set]
[logseq.cli.e2e.cleanup :as cleanup]
[logseq.cli.e2e.coverage :as coverage]
[logseq.cli.e2e.manifests :as manifests]
[logseq.cli.e2e.preflight :as preflight]
@@ -103,6 +104,54 @@
(doseq [{:keys [id]} (manifests/load-cases)]
(println id)))
(defn- print-cleanup-help!
[]
(println "Usage: bb -f cli-e2e/bb.edn cleanup [options]")
(println)
(println "Options:")
(println " -h, --help Show this help and exit")
(println " --dry-run Scan and report only; do not kill/delete")
(println)
(println "Cleanups performed:")
(println " - Terminate cli-e2e db-worker-node processes")
(println " - Remove cli-e2e temp graph directories")
(flush))
(defn cleanup!
[opts]
(if (:help opts)
(do
(print-cleanup-help!)
{:status :help})
(let [dry-run? (boolean (:dry-run opts))
cleanup-opts (cond-> {}
dry-run? (assoc :dry-run true))
processes (cleanup/cleanup-db-worker-processes! cleanup-opts)
temp-graphs (cleanup/cleanup-temp-graph-dirs! cleanup-opts)]
(println "==> Running cli-e2e cleanup")
(if dry-run?
(do
(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] temp graph directories: found %d, would remove %d"
(count (:found-dirs temp-graphs))
(count (:would-remove-dirs temp-graphs)))))
(do
(println (format "db-worker-node processes: found %d, killed %d, failed %d"
(count (:found-pids processes))
(count (:killed-pids processes))
(count (:failed-pids processes))))
(println (format "temp graph directories: found %d, removed %d, failed %d"
(count (:found-dirs temp-graphs))
(count (:removed-dirs temp-graphs))
(count (:failed-dirs temp-graphs))))))
(flush)
{:status :ok
:dry-run? dry-run?
:processes processes
:temp-graphs temp-graphs})))
(defn- print-failure-details!
[error]
(let [data (ex-data error)]

View File

@@ -6,6 +6,7 @@
logseq.cli.e2e.preflight-test
logseq.cli.e2e.shell-test
logseq.cli.e2e.runner-test
logseq.cli.e2e.cleanup-test
logseq.cli.e2e.main-test])
(defn run!

View File

@@ -0,0 +1,94 @@
(ns logseq.cli.e2e.cleanup-test
(:require [babashka.fs :as fs]
[clojure.test :refer [deftest is testing]]
[logseq.cli.e2e.cleanup :as cleanup]))
(deftest list-cli-e2e-db-worker-pids-filters-processes
(let [shell-fn (fn [& _]
{:exit 0
:out (str " 101 node /repo/dist/db-worker-node.js --repo graph-a --data-dir /tmp/logseq-cli-e2e-graph-a-123/graphs --owner-source cli\n"
" 202 node /repo/dist/db-worker-node.js --repo production --data-dir /tmp/production-graphs --owner-source cli\n"
" 303 node /repo/static/logseq-cli.js graph list\n"
" 404 /usr/bin/python3 background.py\n"
" 505 node /repo/static/db-worker-node.js --repo graph-b --data-dir /private/tmp/logseq-cli-e2e-graph-b-999/graphs --owner-source cli\n")
:err ""})]
(is (= [101 505]
(cleanup/list-cli-e2e-db-worker-pids {:shell-fn shell-fn}))))
(testing "throws when ps invocation fails"
(is (thrown-with-msg?
clojure.lang.ExceptionInfo
#"Unable to scan processes"
(cleanup/list-cli-e2e-db-worker-pids {:shell-fn (fn [& _]
{:exit 1
:out ""
:err "permission denied"})})))))
(deftest cleanup-db-worker-processes-separates-killed-and-failed
(let [killed (atom [])
result (cleanup/cleanup-db-worker-processes! {:list-pids-fn (fn [] [11 22 33])
:kill-pid-fn (fn [pid]
(swap! killed conj pid)
(if (= pid 22)
:failed
:killed))})]
(is (= [11 22 33] (:found-pids result)))
(is (= [11 33] (:killed-pids result)))
(is (= [22] (:failed-pids result)))
(is (= [11 22 33] @killed))))
(deftest cleanup-db-worker-processes-dry-run-does-not-kill
(let [killed (atom [])
result (cleanup/cleanup-db-worker-processes! {:dry-run true
:list-pids-fn (fn [] [11 22])
:kill-pid-fn (fn [pid]
(swap! killed conj pid)
:killed)})]
(is (= [11 22] (:found-pids result)))
(is (true? (:dry-run? result)))
(is (= [11 22] (: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")
another-matching-graphs (fs/path tmp-root "logseq-cli-e2e-case-b-456" "graphs")
non-matching-graphs (fs/path tmp-root "other-case" "graphs")]
(try
(fs/create-dirs matching-graphs)
(fs/create-dirs another-matching-graphs)
(fs/create-dirs non-matching-graphs)
(let [result (cleanup/cleanup-temp-graph-dirs! {:tmp-root (str tmp-root)})
expected-dirs (sort [(str matching-graphs)
(str another-matching-graphs)])]
(is (= expected-dirs
(sort (:found-dirs result))))
(is (= expected-dirs
(sort (:removed-dirs result))))
(is (empty? (:failed-dirs result)))
(is (false? (fs/exists? matching-graphs)))
(is (false? (fs/exists? another-matching-graphs)))
(is (true? (fs/exists? non-matching-graphs))))
(finally
(fs/delete-tree tmp-root)))))
(deftest cleanup-temp-graph-dirs-dry-run-does-not-delete
(let [deleted (atom [])
result (cleanup/cleanup-temp-graph-dirs! {:dry-run true
:list-dirs-fn (fn [] ["/tmp/logseq-cli-e2e-a/graphs"
"/tmp/logseq-cli-e2e-b/graphs"])
:delete-dir-fn (fn [dir]
(swap! deleted conj dir))})]
(is (= ["/tmp/logseq-cli-e2e-a/graphs"
"/tmp/logseq-cli-e2e-b/graphs"]
(:found-dirs result)))
(is (true? (:dry-run? result)))
(is (= ["/tmp/logseq-cli-e2e-a/graphs"
"/tmp/logseq-cli-e2e-b/graphs"]
(:would-remove-dirs result)))
(is (= [] (:removed-dirs result)))
(is (= [] (:failed-dirs result)))
(is (= [] @deleted))))

View File

@@ -1,6 +1,7 @@
(ns logseq.cli.e2e.main-test
(:require [clojure.string :as string]
[clojure.test :refer [deftest is testing]]
[logseq.cli.e2e.cleanup :as cleanup]
[logseq.cli.e2e.main :as main]))
(def sample-cases
@@ -189,3 +190,54 @@
(is (string/includes? output "==> Detailed case logging enabled (--case global-help)"))
(is (string/includes? output " [main] $ node static/logseq-cli.js --help"))
(is (true? (:stream-output? @command-opts)))))
(deftest cleanup-help-prints-usage
(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 "--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-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"]
:failed-dirs ["/tmp/logseq-cli-e2e-b/graphs"]})]
(let [result (atom nil)
output (with-out-str
(reset! result (main/cleanup! {})))]
(is (= :ok (:status @result)))
(is (= [101] (get-in @result [: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 "temp graph directories: found 2, removed 1, failed 1")))))
(deftest cleanup-dry-run-prints-summary-and-passes-option
(let [process-opts (atom nil)
dir-opts (atom nil)]
(with-redefs [cleanup/cleanup-db-worker-processes! (fn [opts]
(reset! process-opts opts)
{:dry-run? true
:found-pids [101 202]
:would-kill-pids [101 202]
:killed-pids []
:failed-pids []})
cleanup/cleanup-temp-graph-dirs! (fn [opts]
(reset! dir-opts opts)
{:dry-run? true
:found-dirs ["/tmp/logseq-cli-e2e-a/graphs"]
:would-remove-dirs ["/tmp/logseq-cli-e2e-a/graphs"]
:removed-dirs []
:failed-dirs []})]
(let [result (atom nil)
output (with-out-str
(reset! result (main/cleanup! {:dry-run true})))]
(is (= {:dry-run true} @process-opts))
(is (= {:dry-run true} @dir-opts))
(is (= :ok (:status @result)))
(is (true? (get-in @result [: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] temp graph directories: found 1, would remove 1"))))))