Veröffentlichte Quellen
Quellen als ZIP herunterladen
CH-J SM Mobile
Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.
CH-J / SM Mobile
175 Dateien
sm-mobile-20261016T185235Z-8e93ecc1b59a
Hinzugefügt: · Erstellt:
1
import 'dart:async';2
import 'dart:convert';3
import 'dart:io';4
import 'package:flutter_test/flutter_test.dart';5
import 'package:sm_mobile/services/net/icmp_probe.dart';6
import 'package:sm_mobile/services/net/measure.dart';7
import 'package:sm_mobile/services/net/tcp_ping.dart';9
Sample sample(int seq, double? rtt, [Outcome? outcome]) => Sample(10
seq: seq,11
time: DateTime.utc(2026, 10, 16, 12, 0, seq),12
family: Family.v4,13
address: '192.0.2.1',14
port: 443,15
rttMs: rtt,16
outcome: outcome ?? (rtt == null ? Outcome.timeout : Outcome.ok),17
);19
void main() {20
group('statistics on known values', () {21
test('min, max, mean, median, population stddev, jitter', () {22
final s = RttStats.of([10, 20, 30, 40], attempts: 5);23
expect(s.attempts, 5);24
expect(s.successes, 4);25
expect(s.failures, 1);26
expect(s.successPercent, 80);27
expect(s.lossPercent, 20);28
expect(s.min, 10);29
expect(s.max, 40);30
expect(s.mean, 25);31
expect(s.median, 20, reason: 'nearest rank: ceil(0.5 × 4) = 2nd');32
expect(s.stddev, closeTo(11.1803398875, 1e-9));33
expect(s.jitter, 10);34
expect(s.p95, isNull, reason: 'fewer than 20 samples');35
});37
test('jitter uses consecutive successful samples in order', () {38
expect(RttStats.of([10, 30, 20], attempts: 3).jitter, 15);39
expect(RttStats.of([5], attempts: 1).jitter, isNull);40
});42
test('P95 with 20 samples is the 19th smallest', () {43
final values = [for (var i = 1; i <= 20; i++) i.toDouble()];44
final s = RttStats.of(values.reversed.toList(), attempts: 20);45
expect(s.p95, 19);46
expect(s.median, 10);47
});49
test('no successful attempt: no invented RTT', () {50
final s = RttStats.of(const [], attempts: 3);51
expect(52
(s.min, s.max, s.mean, s.median, s.stddev, s.jitter),53
(null, null, null, null, null, null),54
);55
expect(s.lossPercent, 100);56
expect(RttStats.empty.successPercent, 0);57
});59
test('accumulator equals the exact statistics', () {60
final a = StatsAccumulator();61
final rtts = [12.5, 8.0, null, 15.25, 9.75, null, 11.0];62
for (var i = 0; i < rtts.length; i++) {63
a.add(sample(i + 1, rtts[i]));64
}65
final exact = RttStats.of([66
for (final r in rtts) ?r,67
], attempts: rtts.length);68
final s = a.stats();69
expect(s.attempts, 7);70
expect(s.successes, 5);71
expect(s.mean, closeTo(exact.mean!, 1e-12));72
expect(s.stddev, closeTo(exact.stddev!, 1e-12));73
expect(s.jitter, closeTo(exact.jitter!, 1e-12));74
expect((s.min, s.max, s.median), (exact.min, exact.max, exact.median));75
expect(s.elapsed, const Duration(seconds: 6));76
});78
test('refused and failed attempts never enter RTT statistics', () {79
final a = StatsAccumulator()80
..add(sample(1, 5))81
..add(sample(2, 2, Outcome.refused))82
..add(sample(3, null, Outcome.unreachable));83
final s = a.stats();84
expect((s.attempts, s.successes, s.min, s.max), (3, 1, 5.0, 5.0));85
});87
test('long measurements stay bounded in memory', () {88
final log = SampleLog(100);89
final a = StatsAccumulator();90
for (var i = 0; i < 25000; i++) {91
final s = sample(i, (i % 50).toDouble());92
log.add(s);93
a.add(s);94
}95
expect(log.length, 100);96
expect(log.dropped, 24900);97
final s = a.stats();98
expect(s.attempts, 25000);99
expect(s.windowed, true);100
expect(s.min, 0);101
expect(s.max, 49);102
});103
});105
group('resolution per IP version', () {106
Future<List<InternetAddress>> Function(String, InternetAddressType) records(107
List<String> a,108
List<String> aaaa, {109
Object? error,110
}) => (host, type) async {111
if (error != null) throw error;112
final v4 = [for (final x in a) InternetAddress(x)];113
final v6 = [for (final x in aaaa) InternetAddress(x)];114
return switch (type) {115
InternetAddressType.IPv4 =>116
v4.isEmpty117
? throw const SocketException(118
'no data',119
osError: OSError('no data', 7),120
)121
: v4,122
InternetAddressType.IPv6 => v6,123
_ => [...v6, ...v4],124
};125
};127
test('dual mode with both records gives two independent targets', () async {128
final r = await resolveTarget(129
'both.test',130
IpMode.dual,131
lookup: records(['192.0.2.1'], ['2001:db8::1']),132
);133
expect(r.of(Family.v4)!.addresses.single.address, '192.0.2.1');134
expect(r.of(Family.v6)!.addresses.single.address, '2001:db8::1');135
});137
test('only A or only AAAA: the missing version is named', () async {138
final onlyA = await resolveTarget(139
'a.test',140
IpMode.dual,141
lookup: records(['192.0.2.1'], []),142
);143
expect(onlyA.of(Family.v6)!.missing, 'noAddressFamily');144
expect(onlyA.of(Family.v4)!.available, true);145
final onlyAaaa = await resolveTarget(146
'aaaa.test',147
IpMode.dual,148
lookup: records([], ['2001:db8::2']),149
);150
expect(onlyAaaa.of(Family.v4)!.missing, 'noAddressFamily');151
await expectLater(152
resolveTarget(153
'aaaa.test',154
IpMode.v4,155
lookup: records([], ['2001:db8::2']),156
),157
throwsA(isA<SocketException>()),158
);159
});161
test(162
'automatic mode uses the resolver preference and reports it',163
() async {164
final r = await resolveTarget(165
'both.test',166
IpMode.auto,167
lookup: records(['192.0.2.1'], ['2001:db8::1']),168
);169
expect(r.preferred!.address, '2001:db8::1');170
expect(r.families.map((f) => f.family), [Family.v4, Family.v6]);171
},172
);174
test('broken DNS fails; literals skip DNS', () async {175
await expectLater(176
resolveTarget(177
'x.test',178
IpMode.dual,179
lookup: records([], [], error: const SocketException('down')),180
),181
throwsA(isA<SocketException>()),182
);183
final literal = await resolveTarget('[2001:db8::9]', IpMode.dual);184
expect(literal.of(Family.v6)!.addresses.single.address, '2001:db8::9');185
expect(literal.of(Family.v4)!.missing, 'literalOtherFamily');186
});187
});189
group('TCP connect ping with real sockets', () {190
test('success, refused (not timeout) and count/stop', () async {191
final server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);192
server.listen((s) => s.destroy());193
final ok = await tcpPing(194
InternetAddress.loopbackIPv4,195
TcpPingSettings(196
port: server.port,197
count: 3,198
interval: const Duration(milliseconds: 10),199
),200
).samples.toList();201
expect(ok.map((s) => s.seq), [1, 2, 3]);202
expect(ok.every((s) => s.ok && s.port == server.port), true);203
final port = server.port;204
await server.close();205
final refused = await tcpPing(206
InternetAddress.loopbackIPv4,207
TcpPingSettings(port: port, count: 2, interval: Duration.zero),208
).samples.toList();209
expect(refused.map((s) => s.outcome), [Outcome.refused, Outcome.refused]);211
// Continuous until stopped.212
final run = tcpPing(213
InternetAddress.loopbackIPv4,214
TcpPingSettings(215
port: port,216
count: 0,217
interval: const Duration(milliseconds: 20),218
),219
);220
final got = <Sample>[];221
final done = Completer<void>();222
run.samples.listen(got.add, onDone: done.complete);223
await Future<void>.delayed(const Duration(milliseconds: 250));224
await run.cancel();225
await done.future.timeout(const Duration(seconds: 2));226
final n = got.length;227
expect(n, greaterThan(3));228
await Future<void>.delayed(const Duration(milliseconds: 100));229
expect(got.length, n, reason: 'nothing after stop');230
});232
test('a connect that never completes is a timeout', () async {233
final never = Completer<Socket>();234
final samples = await tcpPing(235
InternetAddress('192.0.2.1'),236
const TcpPingSettings(237
port: 9,238
count: 1,239
timeout: Duration(milliseconds: 50),240
),241
connect: (a, p) async => ConnectionTask.fromSocket(never.future, () {}),242
).samples.toList();243
expect(samples.single.outcome, Outcome.timeout);244
expect(samples.single.rttMs, isNull);245
});247
test('errno classification', () {248
(Outcome, String?) of(int code) => classifyConnectError(249
SocketException('x', osError: OSError('x', code)),250
timedOut: false,251
);252
expect(of(61).$1, Outcome.refused);253
expect(of(111).$1, Outcome.refused);254
expect(of(60).$1, Outcome.timeout);255
expect(of(65).$1, Outcome.unreachable);256
expect(of(101).$1, Outcome.unreachable);257
expect(of(13).$1, Outcome.error);258
});259
});261
group('reports', () {262
Series series() {263
final s = Series(Family.v4, InternetAddress('192.0.2.1'));264
s.add(sample(1, 10));265
s.add(sample(2, null));266
s.add(sample(3, 20));267
return s;268
}270
test('CSV, JSON and text contain every attempt and the statistics', () {271
final csv = pingReport(272
ReportFormat.csv,273
kind: MeasureKind.tcp,274
host: 'host,"x"',275
mode: IpMode.v4,276
settings: {'port': 443},277
series: [series()],278
);279
final lines = csv.trim().split('\n');280
expect(281
lines.first,282
'ip_version,address,port,seq,time_utc,outcome,rtt_ms,detail',283
);284
expect(lines, hasLength(4));285
expect(286
lines[2],287
'IPv4,192.0.2.1,443,2,2026-10-16T12:00:02.000Z,timeout,,',288
);289
final json = jsonDecode(290
pingReport(291
ReportFormat.json,292
kind: MeasureKind.icmp,293
host: 'h',294
mode: IpMode.dual,295
settings: const PingSettings().toJson(),296
series: [series()],297
),298
);299
expect(json['method'], 'ICMP echo');300
final stats = json['series'][0]['statistics'];301
expect(stats['attempts'], 3);302
expect(stats['meanMs'], 15);303
expect(json['series'][0]['samples'], hasLength(3));304
final text = pingReport(305
ReportFormat.txt,306
kind: MeasureKind.tcp,307
host: 'h',308
mode: IpMode.v4,309
settings: const {},310
series: [series()],311
);312
expect(text, contains('#2'));313
expect(text, contains('attempts 3, ok 2, failed 1'));314
expect(315
text,316
contains('rtt min/avg/median/max 10.000/15.000/10.000/20.000'),317
);318
});319
});321
test('traceroute method availability is honest per platform', () {322
// The test host is macOS (Darwin): every method is available.323
if (Platform.isMacOS) {324
for (final m in TraceMethod.values) {325
expect(IcmpProbe.unsupported(m), isNull);326
}327
}328
if (Platform.isLinux || Platform.isAndroid) {329
expect(IcmpProbe.unsupported(TraceMethod.tcp), 'tcpTraceUnsupported');330
}331
});332
}SHA-256
75e741d7f3ab07ecf7e1d4761418b3de13bcdb8854a07d5ac510da98cee39a8dCH-J Proprietary Software License 1.14 · Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.
SHA-256 des Archivs
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655