1327 lines
35 KiB
Dart
1327 lines
35 KiB
Dart
import 'dart:async';
|
||
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:yx_async_throttle_flutter/yx_async_throttle_flutter.dart';
|
||
|
||
/// 商用级全面测试用例
|
||
/// 覆盖所有核心功能、边界条件、并发场景、异常处理、内存安全等
|
||
void main() {
|
||
late AsyncThrottle throttle;
|
||
|
||
setUp(() {
|
||
throttle = AsyncThrottle.instance;
|
||
throttle.clearAllLocks();
|
||
});
|
||
|
||
tearDown(() {
|
||
throttle.clearAllLocks();
|
||
});
|
||
|
||
// ============================================================
|
||
// 1. 单例模式测试
|
||
// ============================================================
|
||
group('Singleton Pattern Tests', () {
|
||
test('多次获取实例应返回同一对象', () {
|
||
final instance1 = AsyncThrottle.instance;
|
||
final instance2 = AsyncThrottle.instance;
|
||
final instance3 = AsyncThrottle.instance;
|
||
|
||
expect(identical(instance1, instance2), isTrue);
|
||
expect(identical(instance2, instance3), isTrue);
|
||
});
|
||
|
||
test('实例不为null', () {
|
||
expect(AsyncThrottle.instance, isNotNull);
|
||
});
|
||
|
||
test('实例类型正确', () {
|
||
expect(AsyncThrottle.instance, isA<AsyncThrottle>());
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 2. 节流模式 (Throttle) 核心功能测试
|
||
// ============================================================
|
||
group('Throttle Mode - Core Functionality', () {
|
||
test('首次调用应立即执行', () async {
|
||
int counter = 0;
|
||
final stopwatch = Stopwatch()..start();
|
||
|
||
await throttle.execute(
|
||
'throttle_immediate',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
duration: const Duration(milliseconds: 100),
|
||
);
|
||
|
||
stopwatch.stop();
|
||
expect(counter, 1);
|
||
// 应该几乎立即执行(小于50ms)
|
||
expect(stopwatch.elapsedMilliseconds, lessThan(50));
|
||
});
|
||
|
||
test('duration内的后续调用应被忽略', () async {
|
||
int counter = 0;
|
||
const duration = Duration(milliseconds: 200);
|
||
|
||
// 第一次调用
|
||
await throttle.execute(
|
||
'throttle_ignore',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
duration: duration,
|
||
);
|
||
|
||
// 立即第二次调用(应被忽略)
|
||
await throttle.execute(
|
||
'throttle_ignore',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
duration: duration,
|
||
);
|
||
|
||
expect(counter, 1);
|
||
});
|
||
|
||
test('duration过后应可以再次执行', () async {
|
||
int counter = 0;
|
||
const duration = Duration(milliseconds: 50);
|
||
|
||
await throttle.execute(
|
||
'throttle_after_duration',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
duration: duration,
|
||
);
|
||
|
||
expect(counter, 1);
|
||
|
||
// 等待 duration 过去
|
||
await Future.delayed(duration + const Duration(milliseconds: 20));
|
||
|
||
await throttle.execute(
|
||
'throttle_after_duration',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
duration: duration,
|
||
);
|
||
|
||
expect(counter, 2);
|
||
});
|
||
|
||
test('异步任务执行中新调用应被忽略(锁机制)', () async {
|
||
int counter = 0;
|
||
final taskCompleter = Completer<void>();
|
||
|
||
// 启动长时间任务
|
||
final future1 = throttle.execute(
|
||
'throttle_lock',
|
||
() async {
|
||
counter++;
|
||
await taskCompleter.future;
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
// 确保任务已开始
|
||
await Future.delayed(const Duration(milliseconds: 10));
|
||
expect(throttle.isExecuting('throttle_lock'), isTrue);
|
||
|
||
// 尝试再次调用(应被锁阻止)
|
||
await throttle.execute(
|
||
'throttle_lock',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
expect(counter, 1);
|
||
|
||
// 完成任务
|
||
taskCompleter.complete();
|
||
await future1;
|
||
expect(throttle.isExecuting('throttle_lock'), isFalse);
|
||
});
|
||
|
||
test('异常后锁应正确释放', () async {
|
||
const duration = Duration(milliseconds: 50);
|
||
bool exceptionThrown = false;
|
||
|
||
try {
|
||
await throttle.execute(
|
||
'throttle_exception_lock',
|
||
() async {
|
||
throw Exception('Test exception');
|
||
},
|
||
enableDebounce: false,
|
||
duration: duration,
|
||
);
|
||
} catch (e) {
|
||
exceptionThrown = true;
|
||
}
|
||
|
||
expect(exceptionThrown, isTrue);
|
||
expect(throttle.isExecuting('throttle_exception_lock'), isFalse);
|
||
|
||
// 等待 duration
|
||
await Future.delayed(duration + const Duration(milliseconds: 20));
|
||
|
||
// 应该可以再次执行
|
||
int counter = 0;
|
||
await throttle.execute(
|
||
'throttle_exception_lock',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
duration: duration,
|
||
);
|
||
|
||
expect(counter, 1);
|
||
});
|
||
|
||
test('异常应正确传播', () async {
|
||
expect(
|
||
() => throttle.execute(
|
||
'throttle_exception_propagate',
|
||
() async {
|
||
throw StateError('Propagated error');
|
||
},
|
||
enableDebounce: false,
|
||
),
|
||
throwsA(isA<StateError>().having((e) => e.message, 'message', 'Propagated error')),
|
||
);
|
||
});
|
||
|
||
test('不同tagId应互不影响', () async {
|
||
int counter1 = 0;
|
||
int counter2 = 0;
|
||
|
||
final future1 = throttle.execute(
|
||
'tag_a',
|
||
() async {
|
||
await Future.delayed(const Duration(milliseconds: 50));
|
||
counter1++;
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
final future2 = throttle.execute(
|
||
'tag_b',
|
||
() async {
|
||
await Future.delayed(const Duration(milliseconds: 50));
|
||
counter2++;
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
await Future.wait([future1, future2]);
|
||
|
||
expect(counter1, 1);
|
||
expect(counter2, 1);
|
||
});
|
||
|
||
test('节流期间多次调用全部被忽略', () async {
|
||
int counter = 0;
|
||
const duration = Duration(milliseconds: 100);
|
||
|
||
// 第一次调用
|
||
await throttle.execute(
|
||
'throttle_multiple_ignore',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
duration: duration,
|
||
);
|
||
|
||
// 快速连续多次调用
|
||
for (int i = 0; i < 10; i++) {
|
||
await throttle.execute(
|
||
'throttle_multiple_ignore',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
duration: duration,
|
||
);
|
||
}
|
||
|
||
expect(counter, 1);
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 3. 防抖模式 (Debounce) 核心功能测试
|
||
// ============================================================
|
||
group('Debounce Mode - Core Functionality', () {
|
||
test('单次调用应延迟执行', () async {
|
||
int counter = 0;
|
||
const duration = Duration(milliseconds: 50);
|
||
|
||
final future = throttle.execute(
|
||
'debounce_delay',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
);
|
||
|
||
// 调用后立即检查
|
||
expect(counter, 0);
|
||
|
||
await future;
|
||
expect(counter, 1);
|
||
});
|
||
|
||
test('快速连续调用只应执行最后一次', () async {
|
||
int counter = 0;
|
||
final executedValues = <int>[];
|
||
const duration = Duration(milliseconds: 50);
|
||
|
||
final futures = <Future<void>>[];
|
||
|
||
for (int i = 1; i <= 5; i++) {
|
||
final value = i;
|
||
futures.add(
|
||
throttle.execute(
|
||
'debounce_last_only',
|
||
() async {
|
||
counter++;
|
||
executedValues.add(value);
|
||
},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
),
|
||
);
|
||
}
|
||
|
||
await Future.delayed(const Duration(milliseconds: 150));
|
||
await Future.wait(futures);
|
||
|
||
expect(counter, 1);
|
||
expect(executedValues, [5]); // 只执行了最后一次
|
||
});
|
||
|
||
test('被取消调用的Future应正确完成(不挂起)', () async {
|
||
const duration = Duration(milliseconds: 50);
|
||
final completedFutures = <int>[];
|
||
|
||
final f1 = throttle.execute(
|
||
'debounce_cancel_complete',
|
||
() async {},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
).then((_) => completedFutures.add(1));
|
||
|
||
final f2 = throttle.execute(
|
||
'debounce_cancel_complete',
|
||
() async {},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
).then((_) => completedFutures.add(2));
|
||
|
||
final f3 = throttle.execute(
|
||
'debounce_cancel_complete',
|
||
() async {},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
).then((_) => completedFutures.add(3));
|
||
|
||
await Future.wait([f1, f2, f3]).timeout(const Duration(seconds: 2));
|
||
|
||
// 所有 Future 都应该完成
|
||
expect(completedFutures.length, 3);
|
||
});
|
||
|
||
test('任务执行中新调用应被阻止', () async {
|
||
int counter = 0;
|
||
final taskCompleter = Completer<void>();
|
||
const duration = Duration(milliseconds: 10);
|
||
|
||
// 启动第一个任务
|
||
final f1 = throttle.execute(
|
||
'debounce_lock_test',
|
||
() async {
|
||
counter++;
|
||
await taskCompleter.future;
|
||
},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
);
|
||
|
||
// 等待防抖触发并开始执行
|
||
await Future.delayed(const Duration(milliseconds: 50));
|
||
expect(throttle.isExecuting('debounce_lock_test'), isTrue);
|
||
|
||
// 尝试新调用(应被阻止)
|
||
final f2 = throttle.execute(
|
||
'debounce_lock_test',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
);
|
||
|
||
await Future.delayed(const Duration(milliseconds: 50));
|
||
|
||
// 完成原任务
|
||
taskCompleter.complete();
|
||
await f1;
|
||
await f2;
|
||
|
||
expect(counter, 1);
|
||
});
|
||
|
||
test('防抖异常应正确传播到最后一个调用者', () async {
|
||
const duration = Duration(milliseconds: 30);
|
||
|
||
final f1 = throttle.execute(
|
||
'debounce_error_propagate',
|
||
() async {
|
||
// 不会执行
|
||
},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
);
|
||
|
||
final f2 = throttle.execute(
|
||
'debounce_error_propagate',
|
||
() async {
|
||
throw ArgumentError('Last call error');
|
||
},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
);
|
||
|
||
// f1 应该正常完成(被取消)
|
||
await expectLater(f1, completes);
|
||
|
||
// f2 应该抛出错误
|
||
await expectLater(f2, throwsA(isA<ArgumentError>()));
|
||
});
|
||
|
||
test('防抖异常后锁应正确释放', () async {
|
||
const duration = Duration(milliseconds: 30);
|
||
|
||
try {
|
||
await throttle.execute(
|
||
'debounce_error_lock',
|
||
() async {
|
||
throw Exception('Test');
|
||
},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
);
|
||
} catch (_) {}
|
||
|
||
expect(throttle.isExecuting('debounce_error_lock'), isFalse);
|
||
|
||
// 应该可以再次执行
|
||
int counter = 0;
|
||
await throttle.execute(
|
||
'debounce_error_lock',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
);
|
||
|
||
await Future.delayed(const Duration(milliseconds: 80));
|
||
expect(counter, 1);
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 4. executeSafe 安全模式测试
|
||
// ============================================================
|
||
group('ExecuteSafe - UI Safe Mode', () {
|
||
test('节流模式异常不应向外抛', () async {
|
||
Object? capturedError;
|
||
|
||
// 不应该抛出异常
|
||
await throttle.executeSafe(
|
||
'safe_throttle',
|
||
() async {
|
||
throw StateError('Should be caught');
|
||
},
|
||
enableDebounce: false,
|
||
onError: (e, s) {
|
||
capturedError = e;
|
||
},
|
||
);
|
||
|
||
expect(capturedError, isA<StateError>());
|
||
});
|
||
|
||
test('防抖模式异常不应向外抛', () async {
|
||
Object? capturedError;
|
||
StackTrace? capturedStack;
|
||
|
||
await throttle.executeSafe(
|
||
'safe_debounce',
|
||
() async {
|
||
throw FormatException('Safe debounce error');
|
||
},
|
||
enableDebounce: true,
|
||
duration: const Duration(milliseconds: 30),
|
||
onError: (e, s) {
|
||
capturedError = e;
|
||
capturedStack = s;
|
||
},
|
||
);
|
||
|
||
await Future.delayed(const Duration(milliseconds: 80));
|
||
|
||
expect(capturedError, isA<FormatException>());
|
||
expect(capturedStack, isNotNull);
|
||
});
|
||
|
||
test('onError回调正确接收错误和堆栈', () async {
|
||
Object? error;
|
||
StackTrace? stack;
|
||
|
||
await throttle.executeSafe(
|
||
'safe_error_callback',
|
||
() async {
|
||
throw UnsupportedError('Test error');
|
||
},
|
||
enableDebounce: false,
|
||
onError: (e, s) {
|
||
error = e;
|
||
stack = s;
|
||
},
|
||
);
|
||
|
||
expect(error, isA<UnsupportedError>());
|
||
expect(stack, isNotNull);
|
||
expect(stack.toString(), isNotEmpty);
|
||
});
|
||
|
||
test('无异常时onError不应被调用', () async {
|
||
bool errorCalled = false;
|
||
|
||
await throttle.executeSafe(
|
||
'safe_no_error',
|
||
() async {
|
||
// 正常执行
|
||
},
|
||
enableDebounce: false,
|
||
onError: (e, s) {
|
||
errorCalled = true;
|
||
},
|
||
);
|
||
|
||
expect(errorCalled, isFalse);
|
||
});
|
||
|
||
test('onError为null时异常被静默吞掉', () async {
|
||
// 不应该抛出任何异常
|
||
await throttle.executeSafe(
|
||
'safe_null_callback',
|
||
() async {
|
||
throw Exception('Silent error');
|
||
},
|
||
enableDebounce: false,
|
||
onError: null,
|
||
);
|
||
|
||
// 如果到达这里,测试通过
|
||
expect(true, isTrue);
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 5. isExecuting 状态检查测试
|
||
// ============================================================
|
||
group('IsExecuting - State Check', () {
|
||
test('执行前应返回false', () {
|
||
expect(throttle.isExecuting('not_started'), isFalse);
|
||
});
|
||
|
||
test('节流执行中应返回true', () async {
|
||
final completer = Completer<void>();
|
||
|
||
final future = throttle.execute(
|
||
'executing_throttle',
|
||
() async {
|
||
await completer.future;
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
await Future.delayed(const Duration(milliseconds: 10));
|
||
expect(throttle.isExecuting('executing_throttle'), isTrue);
|
||
|
||
completer.complete();
|
||
await future;
|
||
expect(throttle.isExecuting('executing_throttle'), isFalse);
|
||
});
|
||
|
||
test('防抖执行中应返回true', () async {
|
||
final completer = Completer<void>();
|
||
|
||
final future = throttle.execute(
|
||
'executing_debounce',
|
||
() async {
|
||
await completer.future;
|
||
},
|
||
enableDebounce: true,
|
||
duration: const Duration(milliseconds: 10),
|
||
);
|
||
|
||
// 等待防抖触发
|
||
await Future.delayed(const Duration(milliseconds: 30));
|
||
expect(throttle.isExecuting('executing_debounce'), isTrue);
|
||
|
||
completer.complete();
|
||
await future;
|
||
expect(throttle.isExecuting('executing_debounce'), isFalse);
|
||
});
|
||
|
||
test('异常后应返回false', () async {
|
||
try {
|
||
await throttle.execute(
|
||
'executing_error',
|
||
() async {
|
||
throw Exception('Test');
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
} catch (_) {}
|
||
|
||
expect(throttle.isExecuting('executing_error'), isFalse);
|
||
});
|
||
|
||
test('不存在的tagId应返回false', () {
|
||
expect(throttle.isExecuting('non_existent_tag_12345'), isFalse);
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 6. clearAllLocks 测试
|
||
// ============================================================
|
||
group('ClearAllLocks - Lock Cleanup', () {
|
||
test('应清除所有锁', () async {
|
||
final completer1 = Completer<void>();
|
||
final completer2 = Completer<void>();
|
||
|
||
throttle.execute('lock1', () async {
|
||
await completer1.future;
|
||
}, enableDebounce: false);
|
||
|
||
throttle.execute('lock2', () async {
|
||
await completer2.future;
|
||
}, enableDebounce: false);
|
||
|
||
await Future.delayed(const Duration(milliseconds: 10));
|
||
|
||
expect(throttle.isExecuting('lock1'), isTrue);
|
||
expect(throttle.isExecuting('lock2'), isTrue);
|
||
|
||
throttle.clearAllLocks();
|
||
|
||
expect(throttle.isExecuting('lock1'), isFalse);
|
||
expect(throttle.isExecuting('lock2'), isFalse);
|
||
|
||
completer1.complete();
|
||
completer2.complete();
|
||
});
|
||
|
||
test('应释放等待中的防抖Completer', () async {
|
||
final future = throttle.execute(
|
||
'pending_debounce',
|
||
() async {},
|
||
enableDebounce: true,
|
||
duration: const Duration(milliseconds: 100),
|
||
);
|
||
|
||
// 立即清除
|
||
throttle.clearAllLocks();
|
||
|
||
// Future 应该正常完成而不是挂起
|
||
await future.timeout(const Duration(seconds: 1));
|
||
});
|
||
|
||
test('清除后异步锁应解除(配合duration过期后可重新执行)', () async {
|
||
final completer = Completer<void>();
|
||
const duration = Duration(milliseconds: 30);
|
||
|
||
throttle.execute('reexecute', () async {
|
||
await completer.future;
|
||
}, enableDebounce: false, duration: duration);
|
||
|
||
await Future.delayed(const Duration(milliseconds: 10));
|
||
|
||
// 此时异步锁生效,clearAllLocks 清除异步锁
|
||
throttle.clearAllLocks();
|
||
expect(throttle.isExecuting('reexecute'), isFalse);
|
||
|
||
// 等待 EasyThrottle 的 duration 过期
|
||
await Future.delayed(duration + const Duration(milliseconds: 20));
|
||
|
||
int counter = 0;
|
||
await throttle.execute('reexecute', () async {
|
||
counter++;
|
||
}, enableDebounce: false, duration: duration);
|
||
|
||
expect(counter, 1);
|
||
completer.complete();
|
||
});
|
||
|
||
test('多次调用clearAllLocks应安全', () {
|
||
throttle.clearAllLocks();
|
||
throttle.clearAllLocks();
|
||
throttle.clearAllLocks();
|
||
expect(true, isTrue); // 无异常则通过
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 7. 并发场景测试
|
||
// ============================================================
|
||
group('Concurrency Tests', () {
|
||
test('多个不同tagId并发执行', () async {
|
||
final results = <String>[];
|
||
final futures = <Future<void>>[];
|
||
|
||
for (int i = 0; i < 10; i++) {
|
||
final tag = 'concurrent_$i';
|
||
futures.add(
|
||
throttle.execute(tag, () async {
|
||
await Future.delayed(const Duration(milliseconds: 10));
|
||
results.add(tag);
|
||
}, enableDebounce: false),
|
||
);
|
||
}
|
||
|
||
await Future.wait(futures);
|
||
expect(results.length, 10);
|
||
});
|
||
|
||
test('大量快速调用压力测试 - 节流', () async {
|
||
int counter = 0;
|
||
const iterations = 100;
|
||
|
||
final futures = <Future<void>>[];
|
||
for (int i = 0; i < iterations; i++) {
|
||
futures.add(
|
||
throttle.execute('stress_throttle', () async {
|
||
counter++;
|
||
}, enableDebounce: false, duration: const Duration(milliseconds: 500)),
|
||
);
|
||
}
|
||
|
||
await Future.wait(futures);
|
||
expect(counter, 1); // 只执行一次
|
||
});
|
||
|
||
test('大量快速调用压力测试 - 防抖', () async {
|
||
int counter = 0;
|
||
const iterations = 100;
|
||
|
||
final futures = <Future<void>>[];
|
||
for (int i = 0; i < iterations; i++) {
|
||
futures.add(
|
||
throttle.execute('stress_debounce', () async {
|
||
counter++;
|
||
}, enableDebounce: true, duration: const Duration(milliseconds: 50)),
|
||
);
|
||
}
|
||
|
||
await Future.delayed(const Duration(milliseconds: 200));
|
||
await Future.wait(futures).timeout(const Duration(seconds: 2));
|
||
|
||
expect(counter, 1); // 只执行最后一次
|
||
});
|
||
|
||
test('混合模式并发', () async {
|
||
int throttleCounter = 0;
|
||
int debounceCounter = 0;
|
||
|
||
final futures = <Future<void>>[];
|
||
|
||
// 节流调用
|
||
for (int i = 0; i < 5; i++) {
|
||
futures.add(
|
||
throttle.execute('mixed_throttle', () async {
|
||
throttleCounter++;
|
||
}, enableDebounce: false),
|
||
);
|
||
}
|
||
|
||
// 防抖调用
|
||
for (int i = 0; i < 5; i++) {
|
||
futures.add(
|
||
throttle.execute('mixed_debounce', () async {
|
||
debounceCounter++;
|
||
}, enableDebounce: true, duration: const Duration(milliseconds: 30)),
|
||
);
|
||
}
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
await Future.wait(futures);
|
||
|
||
expect(throttleCounter, 1);
|
||
expect(debounceCounter, 1);
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 8. 边界条件测试
|
||
// ============================================================
|
||
group('Edge Cases', () {
|
||
test('duration为零应正常工作', () async {
|
||
int counter = 0;
|
||
|
||
await throttle.execute(
|
||
'zero_duration',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
duration: Duration.zero,
|
||
);
|
||
|
||
expect(counter, 1);
|
||
});
|
||
|
||
test('极短duration应正常工作', () async {
|
||
int counter = 0;
|
||
|
||
await throttle.execute(
|
||
'tiny_duration',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
duration: const Duration(microseconds: 1),
|
||
);
|
||
|
||
expect(counter, 1);
|
||
});
|
||
|
||
test('空字符串tagId应正常工作', () async {
|
||
int counter = 0;
|
||
|
||
await throttle.execute(
|
||
'',
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
expect(counter, 1);
|
||
});
|
||
|
||
test('特殊字符tagId应正常工作', () async {
|
||
int counter = 0;
|
||
const specialTag = '!@#\$%^&*()_+-=[]{}|;:,.<>?/~`中文🎉';
|
||
|
||
await throttle.execute(
|
||
specialTag,
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
expect(counter, 1);
|
||
expect(throttle.isExecuting(specialTag), isFalse);
|
||
});
|
||
|
||
test('长字符串tagId应正常工作', () async {
|
||
int counter = 0;
|
||
final longTag = 'a' * 10000;
|
||
|
||
await throttle.execute(
|
||
longTag,
|
||
() async {
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
expect(counter, 1);
|
||
});
|
||
|
||
test('同步完成的异步任务应正常工作', () async {
|
||
int counter = 0;
|
||
|
||
await throttle.execute(
|
||
'sync_async',
|
||
() async {
|
||
counter++; // 同步代码
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
expect(counter, 1);
|
||
});
|
||
|
||
test('嵌套异步任务应正常工作', () async {
|
||
int counter = 0;
|
||
|
||
await throttle.execute(
|
||
'nested_async',
|
||
() async {
|
||
await Future.delayed(const Duration(milliseconds: 10));
|
||
await Future.delayed(const Duration(milliseconds: 10));
|
||
await Future.delayed(const Duration(milliseconds: 10));
|
||
counter++;
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
expect(counter, 1);
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 9. 重入测试
|
||
// ============================================================
|
||
group('Reentrancy Tests', () {
|
||
test('在回调中调用同一tagId应被阻止(无死锁)', () async {
|
||
int outerCounter = 0;
|
||
int innerCounter = 0;
|
||
|
||
await throttle.execute(
|
||
'reentrant_same',
|
||
() async {
|
||
outerCounter++;
|
||
|
||
// 尝试重入
|
||
await throttle
|
||
.execute('reentrant_same', () async {
|
||
innerCounter++;
|
||
}, enableDebounce: false)
|
||
.timeout(const Duration(seconds: 1));
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
expect(outerCounter, 1);
|
||
expect(innerCounter, 0); // 内部调用被阻止
|
||
});
|
||
|
||
test('在回调中调用不同tagId应正常执行', () async {
|
||
int outerCounter = 0;
|
||
int innerCounter = 0;
|
||
|
||
await throttle.execute(
|
||
'reentrant_outer',
|
||
() async {
|
||
outerCounter++;
|
||
|
||
await throttle.execute('reentrant_inner', () async {
|
||
innerCounter++;
|
||
}, enableDebounce: false);
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
expect(outerCounter, 1);
|
||
expect(innerCounter, 1);
|
||
});
|
||
|
||
test('防抖模式重入应安全', () async {
|
||
int outerCounter = 0;
|
||
int innerCounter = 0;
|
||
|
||
final future = throttle.execute(
|
||
'debounce_reentrant_outer',
|
||
() async {
|
||
outerCounter++;
|
||
|
||
await throttle
|
||
.execute('debounce_reentrant_outer', () async {
|
||
innerCounter++;
|
||
}, enableDebounce: true, duration: const Duration(milliseconds: 10))
|
||
.timeout(const Duration(seconds: 1));
|
||
},
|
||
enableDebounce: true,
|
||
duration: const Duration(milliseconds: 10),
|
||
);
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
await future;
|
||
|
||
expect(outerCounter, 1);
|
||
expect(innerCounter, 0);
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 10. 内存安全测试
|
||
// ============================================================
|
||
group('Memory Safety Tests', () {
|
||
test('大量防抖调用后Completer应正确清理', () async {
|
||
const iterations = 200;
|
||
|
||
for (int batch = 0; batch < 5; batch++) {
|
||
final futures = <Future<void>>[];
|
||
for (int i = 0; i < iterations; i++) {
|
||
futures.add(
|
||
throttle.execute('memory_test_$batch', () async {
|
||
// empty
|
||
}, enableDebounce: true, duration: const Duration(milliseconds: 20)),
|
||
);
|
||
}
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
await Future.wait(futures).timeout(const Duration(seconds: 2));
|
||
}
|
||
|
||
// 如果到达这里没有超时或内存问题,测试通过
|
||
expect(true, isTrue);
|
||
});
|
||
|
||
test('异常场景下资源应正确释放', () async {
|
||
for (int i = 0; i < 50; i++) {
|
||
try {
|
||
await throttle.execute(
|
||
'exception_cleanup_$i',
|
||
() async {
|
||
throw Exception('Error $i');
|
||
},
|
||
enableDebounce: i % 2 == 0,
|
||
duration: const Duration(milliseconds: 10),
|
||
);
|
||
} catch (_) {}
|
||
}
|
||
|
||
// 所有锁应该被释放
|
||
for (int i = 0; i < 50; i++) {
|
||
expect(throttle.isExecuting('exception_cleanup_$i'), isFalse);
|
||
}
|
||
});
|
||
|
||
test('clearAllLocks后状态应完全重置', () async {
|
||
// 创建一些状态
|
||
for (int i = 0; i < 10; i++) {
|
||
throttle.execute('cleanup_$i', () async {
|
||
await Future.delayed(const Duration(seconds: 10));
|
||
}, enableDebounce: false);
|
||
}
|
||
|
||
await Future.delayed(const Duration(milliseconds: 50));
|
||
|
||
// 清除所有
|
||
throttle.clearAllLocks();
|
||
|
||
// 验证状态重置
|
||
for (int i = 0; i < 10; i++) {
|
||
expect(throttle.isExecuting('cleanup_$i'), isFalse);
|
||
}
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 11. 时间精度测试
|
||
// ============================================================
|
||
group('Timing Precision Tests', () {
|
||
test('节流duration应基本准确', () async {
|
||
const duration = Duration(milliseconds: 100);
|
||
int counter = 0;
|
||
|
||
await throttle.execute('timing_throttle', () async {
|
||
counter++;
|
||
}, enableDebounce: false, duration: duration);
|
||
|
||
expect(counter, 1);
|
||
|
||
// 在 duration 之前调用应被忽略
|
||
await Future.delayed(const Duration(milliseconds: 50));
|
||
await throttle.execute('timing_throttle', () async {
|
||
counter++;
|
||
}, enableDebounce: false, duration: duration);
|
||
|
||
expect(counter, 1);
|
||
|
||
// 在 duration 之后调用应执行
|
||
await Future.delayed(const Duration(milliseconds: 80));
|
||
await throttle.execute('timing_throttle', () async {
|
||
counter++;
|
||
}, enableDebounce: false, duration: duration);
|
||
|
||
expect(counter, 2);
|
||
});
|
||
|
||
test('防抖duration应基本准确', () async {
|
||
const duration = Duration(milliseconds: 100);
|
||
int counter = 0;
|
||
final stopwatch = Stopwatch()..start();
|
||
|
||
await throttle.execute('timing_debounce', () async {
|
||
counter++;
|
||
}, enableDebounce: true, duration: duration);
|
||
|
||
stopwatch.stop();
|
||
|
||
expect(counter, 1);
|
||
// 执行时间应该大于等于 duration
|
||
expect(stopwatch.elapsedMilliseconds, greaterThanOrEqualTo(90));
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 12. 异常类型测试
|
||
// ============================================================
|
||
group('Exception Type Tests', () {
|
||
test('Error类型应正确传播', () async {
|
||
await expectLater(
|
||
throttle.execute('error_type', () async {
|
||
throw AssertionError('Test assertion');
|
||
}, enableDebounce: false),
|
||
throwsA(isA<AssertionError>()),
|
||
);
|
||
});
|
||
|
||
test('String异常应正确传播', () async {
|
||
await expectLater(
|
||
throttle.execute('string_exception', () async {
|
||
throw 'String exception';
|
||
}, enableDebounce: false),
|
||
throwsA(equals('String exception')),
|
||
);
|
||
});
|
||
|
||
test('自定义异常应正确传播', () async {
|
||
await expectLater(
|
||
throttle.execute('custom_exception', () async {
|
||
throw CustomTestException('Custom error');
|
||
}, enableDebounce: false),
|
||
throwsA(isA<CustomTestException>()),
|
||
);
|
||
});
|
||
|
||
test('防抖模式异常类型应正确传播', () async {
|
||
await expectLater(
|
||
throttle.execute('debounce_exception_type', () async {
|
||
throw RangeError('Out of range');
|
||
}, enableDebounce: true, duration: const Duration(milliseconds: 10)),
|
||
throwsA(isA<RangeError>()),
|
||
);
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 13. 默认参数测试
|
||
// ============================================================
|
||
group('Default Parameters Tests', () {
|
||
test('默认duration应为300ms', () async {
|
||
int counter = 0;
|
||
|
||
await throttle.execute('default_duration', () async {
|
||
counter++;
|
||
});
|
||
|
||
expect(counter, 1);
|
||
|
||
// 在默认 300ms 内再次调用应被忽略
|
||
await throttle.execute('default_duration', () async {
|
||
counter++;
|
||
});
|
||
|
||
expect(counter, 1);
|
||
});
|
||
|
||
test('默认enableDebounce应为false(节流模式)', () async {
|
||
int counter = 0;
|
||
final stopwatch = Stopwatch()..start();
|
||
|
||
await throttle.execute(
|
||
'default_mode',
|
||
() async {
|
||
counter++;
|
||
},
|
||
duration: const Duration(milliseconds: 100),
|
||
);
|
||
|
||
stopwatch.stop();
|
||
|
||
// 节流模式应立即执行
|
||
expect(counter, 1);
|
||
expect(stopwatch.elapsedMilliseconds, lessThan(50));
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 14. 顺序保证测试
|
||
// ============================================================
|
||
group('Order Guarantee Tests', () {
|
||
test('防抖模式应保证只执行最后一次', () async {
|
||
final executedValues = <int>[];
|
||
const duration = Duration(milliseconds: 30);
|
||
|
||
final futures = <Future<void>>[];
|
||
for (int i = 1; i <= 10; i++) {
|
||
final value = i;
|
||
futures.add(
|
||
throttle.execute(
|
||
'order_test',
|
||
() async {
|
||
executedValues.add(value);
|
||
},
|
||
enableDebounce: true,
|
||
duration: duration,
|
||
),
|
||
);
|
||
await Future.delayed(const Duration(milliseconds: 5));
|
||
}
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
await Future.wait(futures);
|
||
|
||
expect(executedValues.length, 1);
|
||
expect(executedValues.first, 10);
|
||
});
|
||
});
|
||
|
||
// ============================================================
|
||
// 15. 复杂场景集成测试
|
||
// ============================================================
|
||
group('Integration Tests', () {
|
||
test('模拟真实按钮快速点击场景', () async {
|
||
int apiCallCount = 0;
|
||
final results = <String>[];
|
||
|
||
// 模拟用户快速点击按钮5次
|
||
for (int i = 0; i < 5; i++) {
|
||
throttle.executeSafe(
|
||
'button_click',
|
||
() async {
|
||
apiCallCount++;
|
||
await Future.delayed(const Duration(milliseconds: 100)); // 模拟API调用
|
||
results.add('success');
|
||
},
|
||
enableDebounce: false,
|
||
duration: const Duration(milliseconds: 300),
|
||
);
|
||
await Future.delayed(const Duration(milliseconds: 50));
|
||
}
|
||
|
||
await Future.delayed(const Duration(milliseconds: 500));
|
||
|
||
expect(apiCallCount, 1);
|
||
expect(results.length, 1);
|
||
});
|
||
|
||
test('模拟搜索输入防抖场景', () async {
|
||
final searchQueries = <String>[];
|
||
|
||
Future<void> search(String query) async {
|
||
await throttle.execute(
|
||
'search_input',
|
||
() async {
|
||
await Future.delayed(const Duration(milliseconds: 50)); // 模拟网络请求
|
||
searchQueries.add(query);
|
||
},
|
||
enableDebounce: true,
|
||
duration: const Duration(milliseconds: 100),
|
||
);
|
||
}
|
||
|
||
// 模拟用户输入 "flutter"
|
||
search('f');
|
||
await Future.delayed(const Duration(milliseconds: 30));
|
||
search('fl');
|
||
await Future.delayed(const Duration(milliseconds: 30));
|
||
search('flu');
|
||
await Future.delayed(const Duration(milliseconds: 30));
|
||
search('flut');
|
||
await Future.delayed(const Duration(milliseconds: 30));
|
||
search('flutt');
|
||
await Future.delayed(const Duration(milliseconds: 30));
|
||
await search('flutter');
|
||
|
||
await Future.delayed(const Duration(milliseconds: 200));
|
||
|
||
// 只有最后一个搜索词被执行
|
||
expect(searchQueries.length, 1);
|
||
expect(searchQueries.first, 'flutter');
|
||
});
|
||
|
||
test('模拟弱网环境重复点击场景', () async {
|
||
int requestCount = 0;
|
||
final taskCompleter = Completer<void>();
|
||
|
||
// 第一次点击,请求开始但未返回
|
||
final f1 = throttle.execute(
|
||
'weak_network',
|
||
() async {
|
||
requestCount++;
|
||
await taskCompleter.future; // 模拟弱网,请求迟迟不返回
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
|
||
await Future.delayed(const Duration(milliseconds: 50));
|
||
|
||
// 用户不耐烦,继续点击
|
||
for (int i = 0; i < 10; i++) {
|
||
await throttle.execute(
|
||
'weak_network',
|
||
() async {
|
||
requestCount++;
|
||
},
|
||
enableDebounce: false,
|
||
);
|
||
}
|
||
|
||
// 请求返回
|
||
taskCompleter.complete();
|
||
await f1;
|
||
|
||
// 应该只有一次请求
|
||
expect(requestCount, 1);
|
||
});
|
||
|
||
test('混合使用节流和防抖应互不干扰', () async {
|
||
int throttleCount = 0;
|
||
int debounceCount = 0;
|
||
|
||
// 同一个tag,先节流后防抖
|
||
await throttle.execute('mixed_tag', () async {
|
||
throttleCount++;
|
||
}, enableDebounce: false);
|
||
|
||
await Future.delayed(const Duration(milliseconds: 350));
|
||
|
||
await throttle.execute('mixed_tag', () async {
|
||
debounceCount++;
|
||
}, enableDebounce: true, duration: const Duration(milliseconds: 30));
|
||
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
|
||
expect(throttleCount, 1);
|
||
expect(debounceCount, 1);
|
||
});
|
||
});
|
||
}
|
||
|
||
/// 自定义测试异常类
|
||
class CustomTestException implements Exception {
|
||
final String message;
|
||
CustomTestException(this.message);
|
||
|
||
@override
|
||
String toString() => 'CustomTestException: $message';
|
||
}
|
||
|