java-mall/mall-common/src/test/com/suisung/mall/common/phone/DistributedLockHelperTest.java

350 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.suisung.mall.common.utils;
import com.suisung.mall.core.web.service.RedisService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.util.ReflectionTestUtils;
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 static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* DistributedLockHelper综合测试套件
*/
public class DistributedLockHelperTest {
private DistributedLockHelper lockHelper;
@Mock
private RedisService redisService;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
lockHelper = new DistributedLockHelper();
ReflectionTestUtils.setField(lockHelper, "redisService", redisService);
System.out.println("=== 初始化测试环境 ===");
}
@Test
public void testTryLock_Success() {
System.out.println("开始执行: testTryLock_Success");
// 给定条件
String lockKey = "test_lock";
long expireTimeSec = 30L;
String expectedKey = "distributed_lock:" + lockKey;
System.out.println("测试参数 - 锁键: " + lockKey + ", 过期时间: " + expireTimeSec + "");
// 当调用时
when(redisService.set(anyString(), anyString(), anyLong())).thenReturn(true);
boolean result = lockHelper.tryLock(lockKey, expireTimeSec);
System.out.println("获取锁结果: " + (result ? "成功" : "失败"));
// 那么验证
assertTrue(result, "锁应该成功获取");
verify(redisService).set(eq(expectedKey), anyString(), eq(expireTimeSec));
System.out.println("验证通过: Redis set 方法被正确调用");
System.out.println("测试完成: testTryLock_Success\n");
}
@Test
public void testTryLock_Failure() {
System.out.println("开始执行: testTryLock_Failure");
// 给定条件
String lockKey = "test_lock";
long expireTimeSec = 30L;
System.out.println("测试参数 - 锁键: " + lockKey + ", 过期时间: " + expireTimeSec + "");
// 当调用时
when(redisService.set(anyString(), anyString(), anyLong())).thenThrow(new RuntimeException("Redis错误"));
System.out.println("模拟Redis异常: Redis错误");
// 那么验证
assertThrows(RuntimeException.class, () -> {
lockHelper.tryLock(lockKey, expireTimeSec);
}, "应该抛出RuntimeException");
System.out.println("验证通过: 正确捕获了RuntimeException");
System.out.println("测试完成: testTryLock_Failure\n");
}
@Test
public void testReleaseLock_Success() {
System.out.println("开始执行: testReleaseLock_Success");
// 给定条件
String lockKey = "test_lock";
String expectedKey = "distributed_lock:" + lockKey;
String lockValue = "uuid:1";
System.out.println("测试参数 - 锁键: " + lockKey + ", 锁值: " + lockValue);
// 设置线程本地变量
ReflectionTestUtils.setField(lockHelper, "LOCK_VALUE_HOLDER",
new ThreadLocal<String>() {
@Override
protected String initialValue() {
return lockValue;
}
});
when(redisService.get(expectedKey)).thenReturn(lockValue);
System.out.println("模拟Redis get返回值: " + lockValue);
// 当调用时
lockHelper.releaseLock(lockKey);
System.out.println("执行锁释放操作");
// 那么验证
verify(redisService).del(expectedKey);
System.out.println("验证通过: Redis del 方法被正确调用");
System.out.println("测试完成: testReleaseLock_Success\n");
}
@Test
public void testReleaseLock_WrongOwner() {
System.out.println("开始执行: testReleaseLock_WrongOwner");
// 给定条件
String lockKey = "test_lock";
String expectedKey = "distributed_lock:" + lockKey;
String currentValue = "uuid:1";
String threadValue = "uuid:2"; // 不同的值
System.out.println("测试参数 - 当前锁值: " + currentValue + ", 线程锁值: " + threadValue);
// 设置线程本地变量
ReflectionTestUtils.setField(lockHelper, "LOCK_VALUE_HOLDER",
new ThreadLocal<String>() {
@Override
protected String initialValue() {
return threadValue;
}
});
when(redisService.get(expectedKey)).thenReturn(currentValue);
System.out.println("模拟Redis中当前锁值: " + currentValue);
// 当调用时
lockHelper.releaseLock(lockKey);
System.out.println("执行锁释放操作");
// 那么验证
verify(redisService, never()).del(anyString());
System.out.println("验证通过: Redis del 方法未被调用(防止误删)");
System.out.println("测试完成: testReleaseLock_WrongOwner\n");
}
@Test
public void testConcurrentAccess_SingleLockAcquirer() throws InterruptedException {
System.out.println("开始执行: testConcurrentAccess_SingleLockAcquirer");
// 给定条件
String lockKey = "concurrent_test_lock";
int threadCount = 10;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
AtomicInteger successCounter = new AtomicInteger(0);
System.out.println("测试参数 - 并发线程数: " + threadCount + ", 锁键: " + lockKey);
// 模拟Redis并发访问行为
when(redisService.set(anyString(), anyString(), anyLong()))
.thenAnswer(invocation -> {
// 只有第一次调用成功,其余都失败
boolean result = successCounter.get() == 0;
if (result) {
System.out.println("线程[" + Thread.currentThread().getName() + "] 成功获取锁");
}
return result;
});
System.out.println("模拟Redis行为: 只允许一个线程获取锁");
// 当执行时
for (int i = 0; i < threadCount; i++) {
final int threadId = i;
executor.submit(() -> {
try {
System.out.println("启动线程[" + threadId + "]");
if (lockHelper.tryLock(lockKey, 30)) {
int count = successCounter.incrementAndGet();
System.out.println("线程[" + threadId + "] 获取锁成功,当前成功计数: " + count);
lockHelper.releaseLock(lockKey);
System.out.println("线程[" + threadId + "] 释放锁");
} else {
System.out.println("线程[" + threadId + "] 获取锁失败");
}
} finally {
latch.countDown();
System.out.println("线程[" + threadId + "] 执行完毕,剩余线程数: " + (latch.getCount()));
}
});
}
boolean completed = latch.await(5, TimeUnit.SECONDS);
executor.shutdown();
System.out.println("所有线程执行完成: " + (completed ? "正常结束" : "超时结束"));
// 那么验证
int successCount = successCounter.get();
assertEquals(1, successCount, "应该只有一个线程获得锁");
System.out.println("验证通过: 实际成功获取锁的线程数为 " + successCount);
System.out.println("测试完成: testConcurrentAccess_SingleLockAcquirer\n");
}
@Test
public void testAutoRenewalFunctionality() throws InterruptedException {
System.out.println("开始执行: testAutoRenewalFunctionality");
// 给定条件
String lockKey = "auto_renew_test_lock";
long expireTimeSec = 5L;
long maxHoldTimeSec = 10L;
System.out.println("测试参数 - 锁键: " + lockKey + ", 过期时间: " + expireTimeSec + "秒, 最大持有时间: " + maxHoldTimeSec + "");
when(redisService.set(anyString(), anyString(), anyLong())).thenReturn(true);
when(redisService.get(anyString())).thenReturn("mock-value");
System.out.println("模拟Redis set/get操作成功");
// 当调用时
DistributedLockHelper.LockHolder lockHolder =
lockHelper.tryLockWithAutoRenew(lockKey, expireTimeSec, maxHoldTimeSec);
System.out.println("获取带自动续期功能的锁");
assertNotNull(lockHolder, "锁持有者不应为空");
System.out.println("锁持有者创建成功");
// 等待可能的续期操作
System.out.println("等待3秒观察自动续期行为...");
Thread.sleep(3000);
// 那么验证
verify(redisService, atLeastOnce()).expire(anyString(), anyLong());
System.out.println("验证通过: Redis expire 方法至少被调用一次");
// 清理
lockHolder.close();
System.out.println("关闭锁持有者,清理资源");
System.out.println("测试完成: testAutoRenewalFunctionality\n");
}
@Test
public void testLockHolderAutoClose() {
System.out.println("开始执行: testLockHolderAutoClose");
// 给定条件
String lockKey = "auto_close_test_lock";
System.out.println("测试参数 - 锁键: " + lockKey);
when(redisService.set(anyString(), anyString(), anyLong())).thenReturn(true);
when(redisService.get(anyString())).thenReturn("mock-value");
System.out.println("模拟Redis操作成功");
// 当调用时
System.out.println("使用try-with-resources语法获取锁");
try (DistributedLockHelper.LockHolder holder =
lockHelper.tryLockWithAutoRenew(lockKey, 30, 300)) {
assertNotNull(holder, "锁持有者不应为空");
System.out.println("锁持有者创建成功");
}
System.out.println("try-with-resources块结束自动调用close方法");
// 那么验证
verify(redisService).del(anyString());
System.out.println("验证通过: Redis del 方法被正确调用");
System.out.println("测试完成: testLockHolderAutoClose\n");
}
@Test
public void testMultipleLocksIndependence() {
System.out.println("开始执行: testMultipleLocksIndependence");
// 给定条件
String lockKey1 = "lock_1";
String lockKey2 = "lock_2";
System.out.println("测试参数 - 锁键1: " + lockKey1 + ", 锁键2: " + lockKey2);
when(redisService.set(anyString(), anyString(), anyLong())).thenReturn(true);
System.out.println("模拟Redis set操作成功");
// 当调用时
boolean lock1Acquired = lockHelper.tryLock(lockKey1, 30);
System.out.println("获取第一个锁: " + (lock1Acquired ? "成功" : "失败"));
boolean lock2Acquired = lockHelper.tryLock(lockKey2, 30);
System.out.println("获取第二个锁: " + (lock2Acquired ? "成功" : "失败"));
// 那么验证
assertTrue(lock1Acquired, "第一个锁应该成功获取");
assertTrue(lock2Acquired, "第二个锁应该成功获取");
System.out.println("两个锁都成功获取");
// 验证它们是不同的键
verify(redisService).set(eq("distributed_lock:" + lockKey1), anyString(), eq(30L));
verify(redisService).set(eq("distributed_lock:" + lockKey2), anyString(), eq(30L));
System.out.println("验证通过: 两个独立的锁键都被正确设置");
System.out.println("测试完成: testMultipleLocksIndependence\n");
}
@Test
public void testThreadIsolation() throws InterruptedException {
System.out.println("开始执行: testThreadIsolation");
// 给定条件
String lockKey = "thread_isolation_lock";
ExecutorService executor = Executors.newFixedThreadPool(2);
CountDownLatch latch = new CountDownLatch(2);
AtomicInteger[] results = new AtomicInteger[]{new AtomicInteger(0), new AtomicInteger(0)};
System.out.println("测试参数 - 锁键: " + lockKey + ", 线程数: 2");
when(redisService.set(anyString(), anyString(), anyLong())).thenReturn(true);
System.out.println("模拟Redis set操作成功");
// 当执行时
System.out.println("启动两个线程竞争同一个锁");
for (int i = 0; i < 2; i++) {
final int threadIndex = i;
executor.submit(() -> {
try {
System.out.println("线程[" + threadIndex + "] 尝试获取锁");
if (lockHelper.tryLock(lockKey, 30)) {
int count = results[threadIndex].incrementAndGet();
System.out.println("线程[" + threadIndex + "] 获取锁成功,计数: " + count);
// 持有锁一段时间
Thread.sleep(100);
System.out.println("线程[" + threadIndex + "] 持有锁100ms后释放");
lockHelper.releaseLock(lockKey);
System.out.println("线程[" + threadIndex + "] 释放锁");
} else {
System.out.println("线程[" + threadIndex + "] 获取锁失败");
}
} catch (InterruptedException e) {
System.out.println("线程[" + threadIndex + "] 被中断");
Thread.currentThread().interrupt();
} finally {
latch.countDown();
System.out.println("线程[" + threadIndex + "] 执行完毕");
}
});
}
boolean completed = latch.await(5, TimeUnit.SECONDS);
executor.shutdown();
System.out.println("所有线程执行完成: " + (completed ? "正常结束" : "超时结束"));
// 那么验证 - 两个线程都应该能够在不同时间获得锁
int result0 = results[0].get();
int result1 = results[1].get();
assertEquals(1, result0, "线程0应该获得一次锁");
assertEquals(1, result1, "线程1应该获得一次锁");
System.out.println("验证通过: 线程0获得锁次数=" + result0 + ", 线程1获得锁次数=" + result1);
System.out.println("测试完成: testThreadIsolation\n");
}
}