mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-05 16:26:42 +10:00
test(tcp): harden cancel-race handling and add coverage
- tighten late TCP connect error suppression to manual-cancel disconnecting/disconnected windows - keep TCP handshake failures surfaced outside explicit cancel flow - allow TcpScreen connect action when connector is scanning - add connector-level tests for late-error suppression classifier - add TcpScreen test covering connect from scanning state
This commit is contained in:
@@ -1059,10 +1059,14 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
await syncTime();
|
await syncTime();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
_appDebugLogService?.error('TCP connection error: $error', tag: 'TCP');
|
_appDebugLogService?.error('TCP connection error: $error', tag: 'TCP');
|
||||||
final tcpConnectNoLongerActive =
|
final tcpConnectCancelledBeforeHandshake =
|
||||||
_activeTransport != MeshCoreTransportType.tcp ||
|
shouldIgnoreLateTcpConnectError(
|
||||||
_state != MeshCoreConnectionState.connecting;
|
manualDisconnect: _manualDisconnect,
|
||||||
if (tcpConnectNoLongerActive) {
|
state: _state,
|
||||||
|
activeTransport: _activeTransport,
|
||||||
|
tcpManagerConnected: _tcpManager.isConnected,
|
||||||
|
);
|
||||||
|
if (tcpConnectCancelledBeforeHandshake) {
|
||||||
_appDebugLogService?.info(
|
_appDebugLogService?.info(
|
||||||
'Ignoring late TCP connect error after cancellation/switch: state=$_state transport=$_activeTransport',
|
'Ignoring late TCP connect error after cancellation/switch: state=$_state transport=$_activeTransport',
|
||||||
tag: 'TCP',
|
tag: 'TCP',
|
||||||
@@ -1074,6 +1078,19 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
static bool shouldIgnoreLateTcpConnectError({
|
||||||
|
required bool manualDisconnect,
|
||||||
|
required MeshCoreConnectionState state,
|
||||||
|
required MeshCoreTransportType activeTransport,
|
||||||
|
required bool tcpManagerConnected,
|
||||||
|
}) {
|
||||||
|
return manualDisconnect &&
|
||||||
|
(state == MeshCoreConnectionState.disconnected ||
|
||||||
|
state == MeshCoreConnectionState.disconnecting) &&
|
||||||
|
(activeTransport != MeshCoreTransportType.tcp || !tcpManagerConnected);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> connect(BluetoothDevice device, {String? displayName}) async {
|
Future<void> connect(BluetoothDevice device, {String? displayName}) async {
|
||||||
if (_state == MeshCoreConnectionState.connecting ||
|
if (_state == MeshCoreConnectionState.connecting ||
|
||||||
_state == MeshCoreConnectionState.connected) {
|
_state == MeshCoreConnectionState.connected) {
|
||||||
|
|||||||
@@ -224,7 +224,11 @@ class _TcpScreenState extends State<TcpScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _connectTcp() async {
|
Future<void> _connectTcp() async {
|
||||||
if (_connector.state != MeshCoreConnectionState.disconnected) return;
|
if (_connector.state == MeshCoreConnectionState.connecting ||
|
||||||
|
_connector.state == MeshCoreConnectionState.connected ||
|
||||||
|
_connector.state == MeshCoreConnectionState.disconnecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final host = _hostController.text.trim();
|
final host = _hostController.text.trim();
|
||||||
final parsedPort = int.tryParse(_portController.text.trim());
|
final parsedPort = int.tryParse(_portController.text.trim());
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:meshcore_open/connector/meshcore_connector.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('shouldIgnoreLateTcpConnectError', () {
|
||||||
|
test('returns true for manual cancel during disconnecting state', () {
|
||||||
|
final result = MeshCoreConnector.shouldIgnoreLateTcpConnectError(
|
||||||
|
manualDisconnect: true,
|
||||||
|
state: MeshCoreConnectionState.disconnecting,
|
||||||
|
activeTransport: MeshCoreTransportType.bluetooth,
|
||||||
|
tcpManagerConnected: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'returns true for manual cancel after reaching disconnected state',
|
||||||
|
() {
|
||||||
|
final result = MeshCoreConnector.shouldIgnoreLateTcpConnectError(
|
||||||
|
manualDisconnect: true,
|
||||||
|
state: MeshCoreConnectionState.disconnected,
|
||||||
|
activeTransport: MeshCoreTransportType.bluetooth,
|
||||||
|
tcpManagerConnected: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('returns false when not a manual disconnect', () {
|
||||||
|
final result = MeshCoreConnector.shouldIgnoreLateTcpConnectError(
|
||||||
|
manualDisconnect: false,
|
||||||
|
state: MeshCoreConnectionState.disconnecting,
|
||||||
|
activeTransport: MeshCoreTransportType.bluetooth,
|
||||||
|
tcpManagerConnected: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false for connected state handshake failures', () {
|
||||||
|
final result = MeshCoreConnector.shouldIgnoreLateTcpConnectError(
|
||||||
|
manualDisconnect: true,
|
||||||
|
state: MeshCoreConnectionState.connected,
|
||||||
|
activeTransport: MeshCoreTransportType.tcp,
|
||||||
|
tcpManagerConnected: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false when TCP is still active while disconnecting', () {
|
||||||
|
final result = MeshCoreConnector.shouldIgnoreLateTcpConnectError(
|
||||||
|
manualDisconnect: true,
|
||||||
|
state: MeshCoreConnectionState.disconnecting,
|
||||||
|
activeTransport: MeshCoreTransportType.tcp,
|
||||||
|
tcpManagerConnected: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, isFalse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -135,6 +135,29 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 60));
|
await tester.pump(const Duration(milliseconds: 60));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('TcpScreen allows connect while connector is scanning', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final connector = _FakeMeshCoreConnector()
|
||||||
|
..initialState = MeshCoreConnectionState.scanning;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
_buildTestApp(
|
||||||
|
connector: connector,
|
||||||
|
child: const TcpScreen(),
|
||||||
|
locale: const Locale('en'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.widgetWithText(FilledButton, 'Connect'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(connector.connectTcpCalls, 1);
|
||||||
|
expect(connector.lastHost, '192.168.40.10');
|
||||||
|
expect(connector.lastPort, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('TcpScreen narrow width long status text does not overflow', (
|
testWidgets('TcpScreen narrow width long status text does not overflow', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|||||||
Reference in New Issue
Block a user