add cycle tests

This commit is contained in:
Tienson Qin
2026-01-15 10:41:50 +08:00
parent 77f4629b22
commit f6114afe47
2 changed files with 90 additions and 59 deletions

View File

@@ -297,10 +297,11 @@
- Computes candidates from remote + rebase tx reports for configured attrs. - Computes candidates from remote + rebase tx reports for configured attrs.
- Iteratively breaks cycles until stable." - Iteratively breaks cycles until stable."
[temp-conn remote-tx-report rebase-tx-report] [temp-conn remote-tx-report rebase-tx-report & {:keys [transact!]
:or {transact! ldb/transact!}}]
(let [remote-touched-by-attr (touched-eids-many (:tx-data remote-tx-report)) (let [remote-touched-by-attr (touched-eids-many (:tx-data remote-tx-report))
local-touched-by-attr (touched-eids-many (:tx-data rebase-tx-report)) local-touched-by-attr (touched-eids-many (:tx-data rebase-tx-report))
candidates-by-attr (union-candidates remote-touched-by-attr local-touched-by-attr) candidates-by-attr (union-candidates remote-touched-by-attr local-touched-by-attr)
touched-info (touched-info-by-attr remote-touched-by-attr local-touched-by-attr)] touched-info (touched-info-by-attr remote-touched-by-attr local-touched-by-attr)]
(when (seq candidates-by-attr) (when (seq candidates-by-attr)
(apply-cycle-repairs! ldb/transact! temp-conn candidates-by-attr touched-info default-attr-opts)))) (apply-cycle-repairs! transact! temp-conn candidates-by-attr touched-info default-attr-opts))))

View File

@@ -1,70 +1,100 @@
(ns logseq.db-sync.cycle-test (ns logseq.db-sync.cycle-test
(:require [cljs.test :refer [deftest is testing]] (:require [cljs.test :refer [deftest is testing]]
[datascript.core :as d] [datascript.core :as d]
[logseq.db.frontend.schema :as db-schema] [logseq.db-sync.cycle :as cycle]
[logseq.db-sync.cycle :as cycle])) [logseq.db.test.helper :as db-test]))
(defn- new-conn [] (defn- new-conn []
(d/create-conn db-schema/schema)) (db-test/create-conn))
(deftest block-parent-cycle-test (defn- fix-cycle!
[temp-conn remote-tx-report rebase-tx-report]
(cycle/fix-cycle! temp-conn remote-tx-report rebase-tx-report {:transact! d/transact!}))
(defn- create-page!
[conn title]
(let [page-uuid (random-uuid)]
(d/transact! conn [{:block/uuid page-uuid
:block/name title
:block/title title}])
page-uuid))
(defn- block-eid
[db uuid]
(d/entid db [:block/uuid uuid]))
(deftest block-parent-2-node-cycle-test
(let [conn (new-conn) (let [conn (new-conn)
page-uuid (create-page! conn "page")
a (random-uuid)
b (random-uuid)]
(d/transact! conn [{:block/uuid a
:block/page [:block/uuid page-uuid]
:block/parent [:block/uuid page-uuid]}
{:block/uuid b
:block/page [:block/uuid page-uuid]
:block/parent [:block/uuid a]}])
(testing "breaks a 2-node :block/parent cycle and reparents to the page"
(let [remote-report (d/transact! conn [{:block/uuid a :block/parent [:block/uuid b]}])]
(fix-cycle! conn remote-report nil)
(let [a' (d/entity @conn [:block/uuid a])
b' (d/entity @conn [:block/uuid b])]
(is (= (block-eid @conn page-uuid) (:db/id (:block/parent a'))))
(is (= (block-eid @conn a) (:db/id (:block/parent b')))))))))
(deftest block-parent-3-node-cycle-test
(let [conn (new-conn)
page-uuid (create-page! conn "page")
a (random-uuid) a (random-uuid)
b (random-uuid) b (random-uuid)
c (random-uuid)] c (random-uuid)]
(d/transact! conn [{:block/uuid a} (d/transact! conn [{:block/uuid a
{:block/uuid b :block/parent [:block/uuid a]} :block/page [:block/uuid page-uuid]
{:block/uuid c :block/parent [:block/uuid b]}]) :block/parent [:block/uuid page-uuid]}
(testing "detects a parent cycle" {:block/uuid b
(let [tx [{:block/uuid a :block/parent [:block/uuid c]}] :block/page [:block/uuid page-uuid]
result (cycle/detect-cycle @conn tx)] :block/parent [:block/uuid a]}
(is (= :block/parent (:attr result))))) {:block/uuid c
(testing "accepts a non-cycle update" :block/page [:block/uuid page-uuid]
(let [tx [{:block/uuid c :block/parent [:block/uuid a]}] :block/parent [:block/uuid b]}])
result (cycle/detect-cycle @conn tx)] (testing "breaks a 3-node :block/parent cycle and reparents to the page"
(is (nil? result)))))) (let [remote-report (d/transact! conn [{:block/uuid a :block/parent [:block/uuid c]}])]
(fix-cycle! conn remote-report nil)
(let [a' (d/entity @conn [:block/uuid a])
b' (d/entity @conn [:block/uuid b])
c' (d/entity @conn [:block/uuid c])]
(is (= (block-eid @conn page-uuid) (:db/id (:block/parent a'))))
(is (= (block-eid @conn a) (:db/id (:block/parent b'))))
(is (= (block-eid @conn b) (:db/id (:block/parent c')))))))))
(deftest class-extends-cycle-test (deftest class-extends-2-node-cycle-test
(let [conn (new-conn)] (let [conn (new-conn)]
(d/transact! conn [{:db/ident :user.class/A :logseq.property.class/extends :user.class/B} (d/transact! conn [{:db/ident :logseq.class/Root}
{:db/ident :user.class/B}
{:db/ident :user.class/A :logseq.property.class/extends :user.class/B}])
(testing "breaks a 2-node :logseq.property.class/extends cycle"
(let [remote-report (d/transact! conn [{:db/ident :user.class/B
:logseq.property.class/extends :user.class/A}])]
(fix-cycle! conn remote-report nil)
(let [b (d/entity @conn :user.class/B)
_ (prn :debug :extends (:logseq.property.class/extends b))
extends (set (map :db/ident (:logseq.property.class/extends b)))]
(is (not (contains? extends :user.class/A)))
(is (contains? extends :logseq.class/Root)))))))
(deftest class-extends-3-node-cycle-with-multiple-values-test
(let [conn (new-conn)]
(d/transact! conn [{:db/ident :logseq.class/Root}
{:db/ident :user.class/C}
{:db/ident :user.class/D}
{:db/ident :user.class/B :logseq.property.class/extends :user.class/C} {:db/ident :user.class/B :logseq.property.class/extends :user.class/C}
{:db/ident :user.class/C}]) {:db/ident :user.class/A :logseq.property.class/extends :user.class/B}])
(let [tx [{:db/ident :user.class/C :logseq.property.class/extends :user.class/A}] (testing "breaks a 3-node :logseq.property.class/extends cycle while preserving other extends"
result (cycle/detect-cycle @conn tx)] (let [remote-report (d/transact! conn [{:db/ident :user.class/C
(is (= :logseq.property.class/extends (:attr result)))))) :logseq.property.class/extends #{:user.class/A :user.class/D}}])]
(fix-cycle! conn remote-report nil)
(deftest server-values-test (let [c (d/entity @conn :user.class/C)
(let [conn (new-conn) extends (set (map :db/ident (:logseq.property.class/extends c)))]
a (random-uuid) (is (not (contains? extends :user.class/A)))
b (random-uuid)] (is (contains? extends :user.class/D))
(d/transact! conn [{:block/uuid a} (is (contains? extends :logseq.class/Root)))))))
{:block/uuid b :block/parent [:block/uuid a]}])
(let [tx [{:block/uuid b :block/parent [:block/uuid b]}]
values (cycle/server-values-for @conn tx :block/parent)]
(is (= {[:block/uuid b] [:block/uuid a]} values)))))
(deftest numeric-entity-cycle-test
(let [conn (new-conn)
a (random-uuid)
b (random-uuid)]
(d/transact! conn [{:block/uuid a}
{:block/uuid b :block/parent [:block/uuid a]}])
(let [a-eid (d/entid @conn [:block/uuid a])
tx [[:db/add a-eid :block/parent [:block/uuid b]]]
result (cycle/detect-cycle @conn tx)]
(is (= :block/parent (:attr result)))
(is (= [:block/uuid a] (:entity result))))))
(deftest three-block-cycle-test
(let [conn (new-conn)
a (random-uuid)
b (random-uuid)
c (random-uuid)]
(d/transact! conn [{:block/uuid a}
{:block/uuid b :block/parent [:block/uuid a]}
{:block/uuid c :block/parent [:block/uuid b]}])
(let [a-eid (d/entid @conn [:block/uuid a])
tx [[:db/add a-eid :block/parent [:block/uuid c]]]
result (cycle/detect-cycle @conn tx)]
(is (= :block/parent (:attr result)))
(is (= [:block/uuid a] (:entity result))))))