test(detection): 添加检测锁管理器的单元测试
- 为 DetectionLockManager 添加完整的单元测试覆盖 - 测试空状态下的获取操作返回成功结果 - 测试被其他用户持有时的获取操作返回忙状态 - 测试同用户重入时刷新页面和过期时间功能 - 测试过期后任意用户可获取锁的功能 - 测试匹配用户时释放锁的操作 - 测试非匹配用户时释放锁的无操作行为 - 测试匹配页面时释放锁的功能 - 测试强制释放锁的操作 - 添加并发获取锁的竞态条件测试确保线程安全
This commit is contained in:
@@ -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<DetectionLock> ref = (AtomicReference<DetectionLock>) 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<String> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,12 +80,12 @@ public class BookmarkUtil {
|
|||||||
P p = (P) element;
|
P p = (P) element;
|
||||||
String textFromP = Docx4jUtil.getTextFromP(p);
|
String textFromP = Docx4jUtil.getTextFromP(p);
|
||||||
if (textFromP.contains("测量回路")) {
|
if (textFromP.contains("测量回路")) {
|
||||||
if (!textFromP.contains("1")) {
|
// if (!textFromP.contains("1")) {
|
||||||
// 另起一页
|
// // 另起一页
|
||||||
P pagePara = Docx4jUtil.getPageBreak();
|
// P pagePara = Docx4jUtil.getPageBreak();
|
||||||
idx = idx + 1;
|
// idx = idx + 1;
|
||||||
parentContent.add(idx, pagePara);
|
// parentContent.add(idx, pagePara);
|
||||||
}
|
// }
|
||||||
idx = idx + 1;
|
idx = idx + 1;
|
||||||
parentContent.add(idx, p);
|
parentContent.add(idx, p);
|
||||||
}
|
}
|
||||||
@@ -169,4 +169,4 @@ public class BookmarkUtil {
|
|||||||
}
|
}
|
||||||
return bookmarkInfo;
|
return bookmarkInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user