From 366d1fadfae9e0b42eb29f8696a3f89c2325637f Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Fri, 12 Jun 2026 19:09:00 +0800 Subject: [PATCH] =?UTF-8?q?test(detection):=20=E6=B7=BB=E5=8A=A0=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E9=94=81=E7=AE=A1=E7=90=86=E5=99=A8=E7=9A=84=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 DetectionLockManager 添加完整的单元测试覆盖 - 测试空状态下的获取操作返回成功结果 - 测试被其他用户持有时的获取操作返回忙状态 - 测试同用户重入时刷新页面和过期时间功能 - 测试过期后任意用户可获取锁的功能 - 测试匹配用户时释放锁的操作 - 测试非匹配用户时释放锁的无操作行为 - 测试匹配页面时释放锁的功能 - 测试强制释放锁的操作 - 添加并发获取锁的竞态条件测试确保线程安全 --- .../lock/DetectionLockManagerTest.java | 151 ++++++++++++++++++ .../tools/report/util/BookmarkUtil.java | 14 +- 2 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 entrance/src/test/java/com/njcn/gather/detection/lock/DetectionLockManagerTest.java diff --git a/entrance/src/test/java/com/njcn/gather/detection/lock/DetectionLockManagerTest.java b/entrance/src/test/java/com/njcn/gather/detection/lock/DetectionLockManagerTest.java new file mode 100644 index 00000000..97b8891e --- /dev/null +++ b/entrance/src/test/java/com/njcn/gather/detection/lock/DetectionLockManagerTest.java @@ -0,0 +1,151 @@ +package com.njcn.gather.detection.lock; + +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class DetectionLockManagerTest { + + private DetectionLockManager manager; + + @Before + public void setUp() throws Exception { + manager = DetectionLockManager.getInstance(); + // 通过反射把 current 清零,避免不同测试方法间状态污染 + Field f = DetectionLockManager.class.getDeclaredField("current"); + f.setAccessible(true); + ((AtomicReference) f.get(manager)).set(null); + } + + @Test + public void tryAcquire_whenEmpty_returnsOk() { + DetectionLockManager.AcquireResult r = manager.tryAcquire("u1", "alice", "page-1"); + assertTrue(r.isOk()); + assertNull(r.getHolder()); + assertEquals("u1", manager.getCurrent().getUserId()); + } + + @Test + public void tryAcquire_whenHeldByOther_returnsBusyWithHolder() { + manager.tryAcquire("u1", "alice", "page-1"); + DetectionLockManager.AcquireResult r = manager.tryAcquire("u2", "bob", "page-2"); + assertFalse(r.isOk()); + assertNotNull(r.getHolder()); + assertEquals("u1", r.getHolder().getHolderUserId()); + assertEquals("alice", r.getHolder().getHolderUserName()); + } + + @Test + public void tryAcquire_reentrantSameUser_refreshesPageAndExpireAt() throws Exception { + manager.tryAcquire("u1", "alice", "page-1"); + long oldAcquire = manager.getCurrent().getAcquireTime(); + long oldExpire = manager.getCurrent().getExpireAt(); + // sleep 50ms(远超 Windows 系统时钟 ~15ms 精度),保证时间戳推进 + Thread.sleep(50); + DetectionLockManager.AcquireResult r = manager.tryAcquire("u1", "alice", "page-2"); + assertTrue(r.isOk()); + assertEquals("page-2", manager.getCurrent().getUserPageId()); + assertTrue("acquireTime 应推进", manager.getCurrent().getAcquireTime() > oldAcquire); + assertTrue("expireAt 应推进", manager.getCurrent().getExpireAt() > oldExpire); + } + + @Test + public void tryAcquire_whenExpired_anyUserCanAcquire() throws Exception { + // 直接构造一个 expireAt 在过去的 lock 写进去 + Field f = DetectionLockManager.class.getDeclaredField("current"); + f.setAccessible(true); + @SuppressWarnings("unchecked") + AtomicReference ref = (AtomicReference) f.get(manager); + long past = System.currentTimeMillis() - 1000; + ref.set(new DetectionLock("u1", "alice", "page-1", past - 1000, past)); + + DetectionLockManager.AcquireResult r = manager.tryAcquire("u2", "bob", "page-2"); + assertTrue(r.isOk()); + assertEquals("u2", manager.getCurrent().getUserId()); + } + + @Test + public void releaseIfHeldBy_matchingUser_clears() { + manager.tryAcquire("u1", "alice", "page-1"); + manager.releaseIfHeldBy("u1", "TEST"); + assertNull(manager.getCurrent()); + } + + @Test + public void releaseIfHeldBy_nonMatchingUser_isNoOp() { + manager.tryAcquire("u1", "alice", "page-1"); + manager.releaseIfHeldBy("u2", "TEST"); + assertNotNull(manager.getCurrent()); + assertEquals("u1", manager.getCurrent().getUserId()); + } + + @Test + public void releaseIfHeldBy_whenEmpty_isNoOp() { + manager.releaseIfHeldBy("u1", "TEST"); + assertNull(manager.getCurrent()); + } + + @Test + public void releaseIfMatchPage_matchingPage_clears() { + manager.tryAcquire("u1", "alice", "page-1"); + manager.releaseIfMatchPage("page-1", "TEST"); + assertNull(manager.getCurrent()); + } + + @Test + public void releaseIfMatchPage_nonMatchingPage_isNoOp() { + manager.tryAcquire("u1", "alice", "page-1"); + manager.releaseIfMatchPage("page-2", "TEST"); + assertNotNull(manager.getCurrent()); + } + + @Test + public void forceRelease_alwaysClears() { + manager.tryAcquire("u1", "alice", "page-1"); + manager.forceRelease("admin", "TEST"); + assertNull(manager.getCurrent()); + } + + @Test + public void concurrentTryAcquire_onlyOneWins() throws Exception { + int n = 100; + ExecutorService pool = Executors.newFixedThreadPool(16); + CountDownLatch start = new CountDownLatch(1); + CountDownLatch done = new CountDownLatch(n); + AtomicInteger okCount = new AtomicInteger(0); + // 记下胜出线程的 userId,便于断言 holder 身份与胜出者一致 + AtomicReference winnerUserId = new AtomicReference<>(null); + for (int i = 0; i < n; i++) { + final String uid = "u" + i; + pool.submit(() -> { + try { + start.await(); + DetectionLockManager.AcquireResult r = manager.tryAcquire(uid, uid, "page-" + uid); + if (r.isOk()) { + okCount.incrementAndGet(); + winnerUserId.set(uid); + } + } catch (InterruptedException ignored) { + } finally { + done.countDown(); + } + }); + } + start.countDown(); + done.await(5, TimeUnit.SECONDS); + pool.shutdownNow(); + assertEquals("100 个不同账号并发只能有 1 个抢到锁", 1, okCount.get()); + assertNotNull(manager.getCurrent()); + // 持锁者必须就是宣称 ok 的那个线程,不能是别人 + assertEquals("holder 身份必须等于胜出线程的 userId", winnerUserId.get(), manager.getCurrent().getUserId()); + } +} diff --git a/tools/report-generator/src/main/java/com/njcn/gather/tools/report/util/BookmarkUtil.java b/tools/report-generator/src/main/java/com/njcn/gather/tools/report/util/BookmarkUtil.java index 7db0613a..92277e29 100644 --- a/tools/report-generator/src/main/java/com/njcn/gather/tools/report/util/BookmarkUtil.java +++ b/tools/report-generator/src/main/java/com/njcn/gather/tools/report/util/BookmarkUtil.java @@ -80,12 +80,12 @@ public class BookmarkUtil { P p = (P) element; String textFromP = Docx4jUtil.getTextFromP(p); if (textFromP.contains("测量回路")) { - if (!textFromP.contains("1")) { - // 另起一页 - P pagePara = Docx4jUtil.getPageBreak(); - idx = idx + 1; - parentContent.add(idx, pagePara); - } +// if (!textFromP.contains("1")) { +// // 另起一页 +// P pagePara = Docx4jUtil.getPageBreak(); +// idx = idx + 1; +// parentContent.add(idx, pagePara); +// } idx = idx + 1; parentContent.add(idx, p); } @@ -169,4 +169,4 @@ public class BookmarkUtil { } return bookmarkInfo; } -} \ No newline at end of file +}