| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'dart:typed_data'; | |
| import 'package:flutter/material.dart'; | |
| import 'pair.dart'; | |
| enum TestStatus { ok, pending, failed, complete } | |
| typedef TestStep = Future<TestStepResult> Function(); | |
| const String nothing = '-'; | |
| /// Result of a test step checking a nested communication handshake | |
| /// between the Flutter app and the platform: | |
| /// | |
| /// - The Flutter app sends a message to the platform. | |
| /// - The platform, on receipt, echos the message back to Flutter in a separate message. | |
| /// - The Flutter app records the incoming message echo and replies. | |
| /// - The platform, on receipt of reply, echos the reply back to Flutter in a separate message. | |
| /// - The Flutter app records the incoming reply echo. | |
| /// - The platform finally replies to the original message with another echo. | |
| class TestStepResult { | |
| const TestStepResult( | |
| this.name, | |
| this.description, | |
| this.status, { | |
| this.messageSent = nothing, | |
| this.messageEcho = nothing, | |
| this.messageReceived = nothing, | |
| this.replyEcho = nothing, | |
| this.error = nothing, | |
| }); | |
| factory TestStepResult.fromSnapshot(AsyncSnapshot<TestStepResult> snapshot) { | |
| switch (snapshot.connectionState) { | |
| case ConnectionState.none: | |
| return const TestStepResult('Not started', nothing, TestStatus.ok); | |
| case ConnectionState.waiting: | |
| return const TestStepResult('Executing', nothing, TestStatus.pending); | |
| case ConnectionState.done: | |
| if (snapshot.hasData) { | |
| return snapshot.data!; | |
| } | |
| return snapshot.error! as TestStepResult; | |
| case ConnectionState.active: | |
| throw 'Unsupported state ${snapshot.connectionState}'; | |
| } | |
| } | |
| final String name; | |
| final String description; | |
| final TestStatus status; | |
| final dynamic messageSent; | |
| final dynamic messageEcho; | |
| final dynamic messageReceived; | |
| final dynamic replyEcho; | |
| final dynamic error; | |
| static const TextStyle bold = TextStyle(fontWeight: FontWeight.bold); | |
| static const TestStepResult complete = TestStepResult( | |
| 'Test complete', | |
| nothing, | |
| TestStatus.complete, | |
| ); | |
| Widget asWidget(BuildContext context) { | |
| return Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: <Widget>[ | |
| Text('Step: $name', style: bold), | |
| Text(description), | |
| const Text(' '), | |
| Text('Msg sent: ${_toString(messageSent)}'), | |
| Text('Msg rvcd: ${_toString(messageReceived)}'), | |
| Text('Reply echo: ${_toString(replyEcho)}'), | |
| Text('Msg echo: ${_toString(messageEcho)}'), | |
| Text('Error: ${_toString(error)}'), | |
| const Text(' '), | |
| Text( | |
| status.toString().substring('TestStatus.'.length), | |
| key: ValueKey<String>( | |
| status == TestStatus.pending ? 'nostatus' : 'status'), | |
| style: bold, | |
| ), | |
| ], | |
| ); | |
| } | |
| static bool deepEquals(dynamic a, dynamic b) => _deepEquals(a, b); | |
| } | |
| Future<TestStepResult> resultOfHandshake( | |
| String name, | |
| String description, | |
| dynamic message, | |
| List<dynamic> received, | |
| dynamic messageEcho, | |
| dynamic error, | |
| ) async { | |
| assert(message != nothing); | |
| while (received.length < 2) { | |
| received.add(nothing); | |
| } | |
| TestStatus status; | |
| if (!_deepEquals(messageEcho, message) || | |
| received.length != 2 || | |
| !_deepEquals(received[0], message) || | |
| !_deepEquals(received[1], message)) { | |
| status = TestStatus.failed; | |
| } else if (error != nothing) { | |
| status = TestStatus.failed; | |
| } else { | |
| status = TestStatus.ok; | |
| } | |
| return TestStepResult( | |
| name, | |
| description, | |
| status, | |
| messageSent: message, | |
| messageEcho: messageEcho, | |
| messageReceived: received[0], | |
| replyEcho: received[1], | |
| error: error, | |
| ); | |
| } | |
| String _toString(dynamic message) { | |
| if (message is ByteData) { | |
| return message.buffer | |
| .asUint8List(message.offsetInBytes, message.lengthInBytes) | |
| .toString(); | |
| } else { | |
| return '$message'; | |
| } | |
| } | |
| bool _deepEquals(dynamic a, dynamic b) { | |
| if (a == b) { | |
| return true; | |
| } | |
| if (a is double && a.isNaN) { | |
| return b is double && b.isNaN; | |
| } | |
| if (a is ByteData) { | |
| return b is ByteData && _deepEqualsByteData(a, b); | |
| } | |
| if (a is List) { | |
| return b is List && _deepEqualsList(a, b); | |
| } | |
| if (a is Map) { | |
| return b is Map && _deepEqualsMap(a, b); | |
| } | |
| if (a is Pair) { | |
| return b is Pair && _deepEqualsPair(a, b); | |
| } | |
| return false; | |
| } | |
| bool _deepEqualsByteData(ByteData a, ByteData b) { | |
| return _deepEqualsList( | |
| a.buffer.asUint8List(a.offsetInBytes, a.lengthInBytes), | |
| b.buffer.asUint8List(b.offsetInBytes, b.lengthInBytes), | |
| ); | |
| } | |
| bool _deepEqualsList(List<dynamic> a, List<dynamic> b) { | |
| if (a.length != b.length) { | |
| return false; | |
| } | |
| for (int i = 0; i < a.length; i++) { | |
| if (!_deepEquals(a[i], b[i])) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) { | |
| if (a.length != b.length) { | |
| return false; | |
| } | |
| for (final dynamic key in a.keys) { | |
| if (!b.containsKey(key) || !_deepEquals(a[key], b[key])) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool _deepEqualsPair(Pair a, Pair b) { | |
| return _deepEquals(a.left, b.left) && _deepEquals(a.right, b.right); | |
| } |