1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// 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;
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 (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);
}