feat: smarter firmware confirm via reconnect
This commit is contained in:
@ -140,6 +140,89 @@ void main() {
|
||||
await transport.dispose();
|
||||
});
|
||||
|
||||
test('completes when FINISH status is lost but bootloader disconnects',
|
||||
() async {
|
||||
final image = _validImage(80);
|
||||
final transport = _FakeFirmwareUpdateTransport(
|
||||
totalBytes: image.length,
|
||||
suppressFinishStatus: true,
|
||||
);
|
||||
final service = FirmwareUpdateService(
|
||||
transport: transport,
|
||||
defaultStatusTimeout: const Duration(milliseconds: 20),
|
||||
defaultPostFinishResetTimeout: const Duration(milliseconds: 100),
|
||||
);
|
||||
|
||||
final result = await service.startUpdate(
|
||||
imageBytes: image,
|
||||
sessionId: 15,
|
||||
);
|
||||
|
||||
expect(result.isOk(), isTrue);
|
||||
expect(
|
||||
transport.controlWrites.last, [universalShifterDfuOpcodeFinish, 15]);
|
||||
expect(transport.steps, contains('reconnectForVerification'));
|
||||
expect(transport.steps, contains('verifyDeviceReachable'));
|
||||
expect(service.currentProgress.state, DfuUpdateState.completed);
|
||||
|
||||
await service.dispose();
|
||||
await transport.dispose();
|
||||
});
|
||||
|
||||
test('fails when FINISH status is lost and bootloader stays connected',
|
||||
() async {
|
||||
final image = _validImage(80);
|
||||
final transport = _FakeFirmwareUpdateTransport(
|
||||
totalBytes: image.length,
|
||||
suppressFinishStatus: true,
|
||||
disconnectAfterFinish: false,
|
||||
);
|
||||
final service = FirmwareUpdateService(
|
||||
transport: transport,
|
||||
defaultStatusTimeout: const Duration(milliseconds: 10),
|
||||
defaultPostFinishResetTimeout: const Duration(milliseconds: 30),
|
||||
);
|
||||
|
||||
final result = await service.startUpdate(
|
||||
imageBytes: image,
|
||||
sessionId: 16,
|
||||
);
|
||||
|
||||
expect(result.isErr(), isTrue);
|
||||
expect(result.unwrapErr().toString(), contains('post-FINISH reset'));
|
||||
expect(service.currentProgress.state, DfuUpdateState.failed);
|
||||
expect(transport.steps, isNot(contains('reconnectForVerification')));
|
||||
|
||||
await service.dispose();
|
||||
await transport.dispose();
|
||||
});
|
||||
|
||||
test('fails when FINISH returns explicit bootloader error', () async {
|
||||
final image = _validImage(80);
|
||||
final transport = _FakeFirmwareUpdateTransport(
|
||||
totalBytes: image.length,
|
||||
finishStatusCode: DfuBootloaderStatusCode.flashError,
|
||||
);
|
||||
final service = FirmwareUpdateService(
|
||||
transport: transport,
|
||||
defaultStatusTimeout: const Duration(milliseconds: 20),
|
||||
defaultPostFinishResetTimeout: const Duration(milliseconds: 100),
|
||||
);
|
||||
|
||||
final result = await service.startUpdate(
|
||||
imageBytes: image,
|
||||
sessionId: 17,
|
||||
);
|
||||
|
||||
expect(result.isErr(), isTrue);
|
||||
expect(result.unwrapErr().toString(), contains('flash error'));
|
||||
expect(service.currentProgress.state, DfuUpdateState.failed);
|
||||
expect(transport.steps, isNot(contains('reconnectForVerification')));
|
||||
|
||||
await service.dispose();
|
||||
await transport.dispose();
|
||||
});
|
||||
|
||||
test('reconnects and resumes from status after transient data failure',
|
||||
() async {
|
||||
final image = _validImage(130);
|
||||
@ -297,6 +380,9 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport {
|
||||
this.suppressFirstDataStatus = false,
|
||||
this.failDataWriteAtOffsetOnce,
|
||||
this.resetSessionOnRecoveryStatus = false,
|
||||
this.suppressFinishStatus = false,
|
||||
this.disconnectAfterFinish = true,
|
||||
this.finishStatusCode = DfuBootloaderStatusCode.ok,
|
||||
this.onDataWrite,
|
||||
});
|
||||
|
||||
@ -308,6 +394,9 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport {
|
||||
final bool suppressFirstDataStatus;
|
||||
final int? failDataWriteAtOffsetOnce;
|
||||
final bool resetSessionOnRecoveryStatus;
|
||||
final bool suppressFinishStatus;
|
||||
final bool disconnectAfterFinish;
|
||||
final DfuBootloaderStatusCode finishStatusCode;
|
||||
final void Function()? onDataWrite;
|
||||
|
||||
final StreamController<List<int>> _statusController =
|
||||
@ -323,6 +412,7 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport {
|
||||
bool _sentDataFailure = false;
|
||||
bool _sentQueueFull = false;
|
||||
bool _suppressedDataStatus = false;
|
||||
bool _finishDisconnectAvailable = false;
|
||||
|
||||
@override
|
||||
Future<Result<bool>> isConnectedToBootloader() async {
|
||||
@ -389,7 +479,11 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport {
|
||||
}
|
||||
_scheduleStatus(DfuBootloaderStatusCode.ok, _sessionId, _expectedOffset);
|
||||
} else if (opcode == universalShifterDfuOpcodeFinish) {
|
||||
_scheduleStatus(DfuBootloaderStatusCode.ok, payload[1], totalBytes);
|
||||
if (suppressFinishStatus) {
|
||||
_finishDisconnectAvailable = disconnectAfterFinish;
|
||||
} else {
|
||||
_scheduleStatus(finishStatusCode, payload[1], totalBytes);
|
||||
}
|
||||
} else if (opcode == universalShifterDfuOpcodeAbort) {
|
||||
_scheduleStatus(DfuBootloaderStatusCode.ok, payload[1], 0);
|
||||
}
|
||||
@ -431,7 +525,11 @@ class _FakeFirmwareUpdateTransport implements FirmwareUpdateTransport {
|
||||
@override
|
||||
Future<Result<void>> waitForBootloaderDisconnect(
|
||||
{required Duration timeout}) async {
|
||||
if (timeout == Duration.zero && !_finishDisconnectAvailable) {
|
||||
return bail('still connected');
|
||||
}
|
||||
steps.add('waitForBootloaderDisconnect');
|
||||
_finishDisconnectAvailable = true;
|
||||
return Ok(null);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user