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() { @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() { @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"); } }