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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user