Nailathalang source
I-download ang source ZIP
CH-J SM Mobile
I-browse ang mga direktoryo at file ng isang partikular na release ng application.
CH-J / SM Mobile
175 file
sm-mobile-20261016T185235Z-8e93ecc1b59a
Idinagdag: · Binuo:
1
import 'dart:async';2
import 'dart:io';3
import 'package:flutter/material.dart';4
import 'package:flutter_test/flutter_test.dart';5
import 'package:intl/intl.dart';6
import 'package:sm_mobile/core/app_controller.dart';7
import 'package:sm_mobile/l10n/strings.dart';8
import 'package:sm_mobile/services/diagnostics.dart';9
import 'package:sm_mobile/services/metrics_format.dart';10
import 'package:sm_mobile/services/net/icmp_probe.dart';11
import 'package:sm_mobile/services/net/measure.dart';12
import 'package:sm_mobile/services/net/tcp_ping.dart';13
import 'package:sm_mobile/ui/diagnostics_page.dart';14
import 'support.dart';16
String decimal(double v, int digits) => NumberFormat.decimalPatternDigits(17
locale: 'cs',18
decimalDigits: digits,19
).format(v);21
void main() {22
group('metric formatting', () {23
// Values as LinuxMonitor reports them (observed on Ubuntu 24.04.5).24
const sample = {25
'cpu': '1.0%',26
'ram': '15.7% (308 / 1967 MiB)',27
'disk': '17% (4521312 / 30298176 KiB)',28
'uptime': '166790.65',29
'load': '0.19 0.11 0.04',30
'rtt': '12 ms',31
};33
test('parses the monitor output into numbers', () {34
final v = MetricValues.parse(sample);35
expect(v.cpu, 1.0);36
expect(v.ramPercent, 15.7);37
expect(v.ramUsedBytes, 308 * 1024 * 1024);38
expect(v.ramTotalBytes, 1967 * 1024 * 1024);39
expect(v.diskPercent, 17);40
expect(v.diskUsedBytes, 4521312 * 1024);41
expect(v.diskTotalBytes, 30298176 * 1024);42
expect(v.uptimeSeconds, 166790.65);43
expect(v.load, [0.19, 0.11, 0.04]);44
});46
test('unexpected output stays unparsed instead of guessed', () {47
final v = MetricValues.parse(const {48
'cpu': 'n/a',49
'ram': 'weird',50
'uptime': '-5',51
'load': '0.1 x 0.2',52
});53
expect(v.cpu, isNull);54
expect(v.ramPercent, isNull);55
expect(v.ramUsedBytes, isNull);56
expect(v.uptimeSeconds, isNull);57
expect(v.load, isNull);58
expect(MetricValues.parse(const {}).diskPercent, isNull);59
});61
test('uptime splits into days, hours and minutes', () {62
expect(splitUptime(166790.65), (days: 1, hours: 22, minutes: 19));63
expect(splitUptime(59), (days: 0, hours: 0, minutes: 0));64
expect(splitUptime(3600), (days: 0, hours: 1, minutes: 0));65
expect(splitUptime(86400 * 400 + 61), (days: 400, hours: 0, minutes: 1));66
});68
test('uptime is localized with correct plural forms', () async {69
final cs = await AppLocalizations.delegate.load(const Locale('cs'));70
final de = await AppLocalizations.delegate.load(const Locale('de'));71
final en = await AppLocalizations.delegate.load(const Locale('en'));72
String text(AppLocalizations l, double s) {73
final u = splitUptime(s);74
return [75
if (u.days > 0) l.uptimeDays(u.days),76
if (u.days > 0 || u.hours > 0) l.uptimeHours(u.hours),77
l.uptimeMinutes(u.minutes),78
].join(' ');79
}81
expect(text(cs, 166790.65), '1 den 22 hodin 19 minut');82
expect(83
text(cs, 3 * 86400 + 2 * 3600 + 4 * 60),84
'3 dny 2 hodiny 4 minuty',85
);86
expect(text(cs, 5 * 86400 + 60), '5 dní 0 hodin 1 minuta');87
expect(text(en, 166790.65), '1 day 22 hours 19 minutes');88
expect(text(de, 166790.65), '1 Tag 22 Stunden 19 Minuten');89
});91
test('sizes use binary units consistently', () {92
expect(formatBytes(512, decimal), '512 B');93
expect(formatBytes(308 * 1024 * 1024, decimal), '308 MiB');94
expect(formatBytes(1967 * 1024 * 1024, decimal), '1,9 GiB');95
expect(formatBytes(4521312 * 1024, decimal), '4,3 GiB');96
expect(formatBytes(30298176 * 1024, decimal), '28,9 GiB');97
expect(formatBytes(3 * 1024 * 1024 * 1024 * 1024, decimal), '3,0 TiB');98
});99
});101
group('diagnostics page (simulated network, no sockets)', () {102
late List<Object> started;103
late Map<InternetAddress, StreamController<Object>> streams;104
late bool cancelled;106
NetTools tools({107
List<String> a = const ['192.0.2.5'],108
List<String> aaaa = const ['2001:db8::5'],109
Object? lookupError,110
String? Function(TraceMethod)? unsupported,111
}) {112
started = [];113
streams = {};114
cancelled = false;115
return NetTools(116
lookup: (host, type) async {117
if (lookupError != null) throw lookupError;118
final v4 = [for (final x in a) InternetAddress(x)];119
final v6 = [for (final x in aaaa) InternetAddress(x)];120
return switch (type) {121
InternetAddressType.IPv4 => v4,122
InternetAddressType.IPv6 => v6,123
_ => [...v6, ...v4],124
};125
},126
tcp: (address, settings) {127
started.add(settings);128
final c = StreamController<Sample>();129
streams[address] = c;130
return PingRun(c.stream, () async {131
cancelled = true;132
unawaited(c.close());133
});134
},135
ping: (address, settings) {136
started.add(settings);137
final c = StreamController<ProbeEvent>();138
streams[address] = c;139
return IcmpRun(c.stream, () async {140
cancelled = true;141
unawaited(c.close());142
});143
},144
traceroute: (address, settings) {145
started.add(settings);146
final c = StreamController<ProbeEvent>();147
streams[address] = c;148
return IcmpRun(c.stream, () async {149
cancelled = true;150
unawaited(c.close());151
});152
},153
reverse: (a) async =>154
a.address == '198.51.100.1' ? 'router.example' : null,155
unsupported: unsupported ?? (_) => null,156
);157
}159
Future<AppController> pump(WidgetTester tester, NetTools tools) async {160
tester.view.physicalSize = const Size(900, 4000);161
tester.view.devicePixelRatio = 1;162
addTearDown(tester.view.reset);163
final c = AppController(MemoryStore());164
await tester.runAsync(() async {165
await c.initialize();166
await c.acceptLicense();167
await c.openVault('diagnostics ui test', create: true);168
});169
await tester.pumpWidget(170
MaterialApp(171
locale: const Locale('en'),172
localizationsDelegates: AppLocalizations.localizationsDelegates,173
supportedLocales: AppLocalizations.supportedLocales,174
home: Scaffold(body: DiagnosticsPage(c, tools: tools)),175
),176
);177
await tester.pumpAndSettle();178
return c;179
}181
Sample tcpSample(int seq, String address, double? rtt, Outcome outcome) =>182
Sample(183
seq: seq,184
time: DateTime(2026, 10, 16, 12, 0, seq),185
family: address.contains(':') ? Family.v6 : Family.v4,186
address: address,187
port: 443,188
rttMs: rtt,189
outcome: outcome,190
);192
// Measurements animate a progress bar, so the page never settles while193
// one runs; pump a few frames instead.194
Future<void> step(WidgetTester tester) async {195
for (var i = 0; i < 5; i++) {196
await tester.pump(const Duration(milliseconds: 50));197
}198
}200
Future<void> finish(WidgetTester tester, AppController c) async {201
await tester.pumpWidget(const SizedBox());202
c.dispose();203
}205
testWidgets('TCP ping: count presets, every attempt listed, statistics', (206
tester,207
) async {208
final c = await pump(tester, tools());209
await tester.enterText(find.byType(TextField).at(0), 'server.test');210
await tester.enterText(find.byType(TextField).at(1), '443');211
await tester.tap(find.text('IPv4'));212
await tester.tap(find.byType(DropdownButtonFormField<String>));213
await tester.pumpAndSettle();214
await tester.tap(find.text('10').last);215
await tester.pumpAndSettle();216
await tester.tap(find.text('Run test'));217
await step(tester);218
final s = started.single as TcpPingSettings;219
expect((s.port, s.count), (443, 10));220
final c4 =221
streams[InternetAddress('192.0.2.5')]! as StreamController<Sample>;222
c4223
..add(tcpSample(1, '192.0.2.5', 10, Outcome.ok))224
..add(tcpSample(2, '192.0.2.5', null, Outcome.timeout))225
..add(tcpSample(3, '192.0.2.5', 1.5, Outcome.refused))226
..add(tcpSample(4, '192.0.2.5', 30, Outcome.ok));227
await step(tester);228
expect(find.text('Timeout'), findsOneWidget);229
expect(find.textContaining('Refused (TCP reset)'), findsOneWidget);230
expect(find.text('10.0 ms'), findsWidgets);231
Text value(String label) => tester.widget<Text>(232
find233
.descendant(234
of: find235
.ancestor(of: find.text(label), matching: find.byType(Column))236
.first,237
matching: find.byType(Text),238
)239
.last,240
);241
expect(value('Attempts').data, '4');242
expect(value('Successful').data, '2');243
expect(value('Failed').data, '2');244
expect(value('Success').data, '50.0 %');245
expect(value('Average').data, '20.0 ms');246
expect(value('Jitter').data, '20.0 ms');247
expect(value('P95').data, '—');248
await tester.tap(find.text('Stop'));249
await step(tester);250
expect(cancelled, true);251
expect(find.text('Run test'), findsOneWidget);252
await finish(tester, c);253
});255
testWidgets('both IP versions run independently; a missing AAAA is shown', (256
tester,257
) async {258
var c = await pump(tester, tools());259
await tester.enterText(find.byType(TextField).at(0), 'dual.test');260
await tester.tap(find.text('4 + 6'));261
await tester.tap(find.text('Run test'));262
await step(tester);263
expect(started, hasLength(2));264
expect(streams.keys.map((a) => a.address).toSet(), {265
'192.0.2.5',266
'2001:db8::5',267
});268
(streams[InternetAddress('2001:db8::5')]! as StreamController<Sample>)269
.add(tcpSample(1, '2001:db8::5', null, Outcome.timeout));270
(streams[InternetAddress('192.0.2.5')]! as StreamController<Sample>).add(271
tcpSample(1, '192.0.2.5', 12, Outcome.ok),272
);273
await tester.pump();274
expect(find.text('IPv4 and IPv6 overview'), findsOneWidget);275
expect(276
find.text('0.0 % (0/1)'),277
findsOneWidget,278
reason: 'IPv6 failure is visible',279
);280
expect(find.text('100.0 % (1/1)'), findsOneWidget);281
await finish(tester, c);283
c = await pump(tester, tools(aaaa: const []));284
await tester.enterText(find.byType(TextField).at(0), 'only-a.test');285
await tester.tap(find.text('4 + 6'));286
await tester.tap(find.text('Run test'));287
await step(tester);288
expect(started, hasLength(1));289
expect(290
find.textContaining('No address of this IP version'),291
findsOneWidget,292
);293
await finish(tester, c);294
});296
testWidgets('ICMP ping options, address actually used, stop on demand', (297
tester,298
) async {299
final c = await pump(tester, tools());300
await tester.tap(find.text('ICMP ping'));301
await tester.pump();302
expect(find.text('TCP port'), findsNothing, reason: 'ICMP has no port');303
await tester.enterText(find.byType(TextField).first, 'server.test');304
await tester.tap(find.byType(DropdownButtonFormField<String>));305
await tester.pumpAndSettle();306
await tester.tap(find.text('Continuous until stopped').last);307
await tester.pumpAndSettle();308
await tester.tap(find.text('Test options'));309
await tester.pumpAndSettle();310
await tester.enterText(311
find.widgetWithText(TextField, 'Payload size (B)'),312
'100',313
);314
await tester.enterText(315
find.widgetWithText(TextField, 'TTL / hop limit'),316
'7',317
);318
await tester.tap(find.text("Don't fragment (DF)"));319
await tester.tap(find.text('Run test'));320
await step(tester);321
final s = started.single as PingSettings;322
expect((s.count, s.size, s.ttl, s.dontFragment), (0, 100, 7, true));323
// Automatic mode: the resolver preferred IPv6, and the page says so.324
expect(find.textContaining('IPv6 / ICMPv6 echo'), findsOneWidget);325
final from = InternetAddress('2001:db8::5');326
(streams[from]! as StreamController<ProbeEvent>)327
..add(ProbeEvent(1, ProbeStatus.reply, from: from, rttMs: 10))328
..add(const ProbeEvent(2, ProbeStatus.timeout))329
..add(ProbeEvent(3, ProbeStatus.reply, from: from, rttMs: 30));330
await step(tester);331
expect(find.text('Timeout'), findsOneWidget);332
await tester.tap(find.text('Stop'));333
await step(tester);334
expect(cancelled, true);335
expect(find.textContaining('Stopped by user'), findsOneWidget);336
await finish(tester, c);337
});339
testWidgets('platform errors are explained, not hidden', (tester) async {340
final c = await pump(341
tester,342
tools(a: const ['192.0.2.9'], aaaa: const []),343
);344
await tester.tap(find.text('ICMP ping'));345
await tester.pump();346
await tester.enterText(find.byType(TextField).first, '192.0.2.9');347
await tester.tap(find.text('Run test'));348
await step(tester);349
final s =350
streams[InternetAddress('192.0.2.9')]!351
as StreamController<ProbeEvent>;352
s.addError(const IcmpUnavailable('icmpNotPermitted', 13));353
await s.close();354
await step(tester);355
expect(356
find.textContaining('does not allow an unprivileged ICMP'),357
findsWidgets,358
);359
await finish(tester, c);360
});362
testWidgets('traceroute: hops, *, names, statistics, compare and export', (363
tester,364
) async {365
final c = await pump(366
tester,367
tools(a: const ['192.0.2.9'], aaaa: const []),368
);369
await tester.tap(find.text('Traceroute'));370
await tester.pump();371
await tester.enterText(find.byType(TextField).first, '192.0.2.9');372
await tester.tap(find.text('UDP'));373
await tester.tap(find.text('Run test'));374
await step(tester);375
final settings = started.single as TraceSettings;376
expect(377
(settings.method, settings.maxHops, settings.probes),378
(TraceMethod.udp, 30, 3),379
);380
final router = InternetAddress('198.51.100.1');381
final target = InternetAddress('192.0.2.9');382
final s = streams[target]! as StreamController<ProbeEvent>;383
s384
..add(385
ProbeEvent(386
1,387
ProbeStatus.timeExceeded,388
ttl: 1,389
from: router,390
rttMs: 1.5,391
),392
)393
..add(const ProbeEvent(2, ProbeStatus.timeout, ttl: 1))394
..add(395
ProbeEvent(396
3,397
ProbeStatus.timeExceeded,398
ttl: 1,399
from: router,400
rttMs: 2.5,401
),402
)403
..add(const ProbeEvent(1, ProbeStatus.timeout, ttl: 2))404
..add(405
ProbeEvent(406
1,407
ProbeStatus.reply,408
ttl: 3,409
from: target,410
rttMs: 12,411
icmpCode: 3,412
note: 'portUnreachable',413
),414
);415
await s.close();416
await step(tester);417
expect(find.text('router.example (198.51.100.1)'), findsOneWidget);418
expect(find.text('1.50 ms * 2.50 ms'), findsOneWidget);419
expect(420
find.textContaining('min / avg / max: 1.50 / 2.00 / 2.50 ms'),421
findsOneWidget,422
);423
expect(find.textContaining('Destination reached'), findsOneWidget);424
expect(find.textContaining('Protocol: UDP'), findsOneWidget);425
// A second run can be compared with the first.426
await tester.tap(find.text('Run test'));427
await step(tester);428
final again = streams[target]! as StreamController<ProbeEvent>;429
again.add(430
ProbeEvent(431
1,432
ProbeStatus.timeExceeded,433
ttl: 1,434
from: InternetAddress('198.51.100.7'),435
rttMs: 3,436
),437
);438
await again.close();439
await step(tester);440
await tester.tap(find.text('Compare with the previous measurement'));441
await tester.pumpAndSettle();442
expect(find.textContaining('Route changed'), findsOneWidget);443
await finish(tester, c);444
});446
testWidgets('an unsupported traceroute method cannot be chosen', (447
tester,448
) async {449
final c = await pump(450
tester,451
tools(452
unsupported: (m) =>453
m == TraceMethod.tcp ? 'tcpTraceUnsupported' : null,454
),455
);456
await tester.tap(find.text('Traceroute'));457
await tester.pump();458
expect(459
find.textContaining('TCP: Not available on Android'),460
findsOneWidget,461
);462
await tester.tap(find.text('TCP'));463
await tester.pump();464
final selected = tester465
.widget<SegmentedButton<TraceMethod>>(466
find.byType(SegmentedButton<TraceMethod>),467
)468
.selected;469
expect(selected, {TraceMethod.icmp});470
await finish(tester, c);471
});473
testWidgets('DNS failure and invalid options never start a measurement', (474
tester,475
) async {476
final c = await pump(477
tester,478
tools(lookupError: const SocketException('Failed host lookup')),479
);480
await tester.enterText(481
find.byType(TextField).first,482
'nonexistent.invalid',483
);484
await tester.tap(find.text('Run test'));485
await step(tester);486
expect(started, isEmpty);487
expect(find.textContaining('DNS lookup failed'), findsOneWidget);488
await tester.enterText(find.byType(TextField).at(1), '70000');489
await tester.tap(find.text('Run test'));490
await step(tester);491
expect(started, isEmpty);492
expect(find.text('Invalid value in the test options.'), findsOneWidget);493
await finish(tester, c);494
});495
});497
test('real sockets: TCP failure is reported as the TCP stage', () async {498
final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);499
final port = server.port;500
final ok = await Diagnostics().probe(501
'127.0.0.1',502
port,503
type: InternetAddressType.IPv4,504
);505
expect(ok.connected!.address, '127.0.0.1');506
expect(ok.dnsMs, isNotNull);507
await server.close();508
await expectLater(509
Diagnostics().probe('127.0.0.1', port, type: InternetAddressType.IPv4),510
throwsA(511
isA<ProbeFailure>().having((e) => e.stage, 'stage', 'tcp').having(512
(e) => e.addresses,513
'addresses',514
['127.0.0.1'],515
),516
),517
);518
});519
}SHA-256
d17d7645a7a7bcd3c3537b3154817096619f02a4cc3551434ec7501d2f1beb5bCH-J Proprietary Software License 1.14 · Ibinibigay ang source sa ilalim ng CH-J Proprietary Software License 1.14. Hindi binabago ng pagiging available nito ang mga tuntunin ng lisensya at hindi ito nagbibigay ng karagdagang karapatan.
SHA-256 ng archive
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655