// Copyright 2017 The Chromium 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; } else { final TestStepResult result = snapshot.error; return result; } break; default: 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, ), ], ); } } 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 (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); }