CH-J SM Mobileمدیریت سرور از طریق SSH
منو
منبع منتشرشده

CH-J SM Mobile

پوشه‌ها و فایل‌های یک نسخهٔ مشخص از برنامه را مرور کنید.

دانلود ZIP منبع
CH-J / SM Mobile 175 فایل
sm-mobile-20261016T185235Z-8e93ecc1b59a افزوده‌شده: · ساخته‌شده:
measure_test.dart افزوده‌شده: تاریخ ثبت نشده است ۱۰٫۸ KB · 332 خط
کد دانلود فایل
1import 'dart:async';
2import 'dart:convert';
3import 'dart:io';
4import 'package:flutter_test/flutter_test.dart';
5import 'package:sm_mobile/services/net/icmp_probe.dart';
6import 'package:sm_mobile/services/net/measure.dart';
7import 'package:sm_mobile/services/net/tcp_ping.dart';
9Sample 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);
19void 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.isEmpty
117 ? 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 });
SHA-25675e741d7f3ab07ecf7e1d4761418b3de13bcdb8854a07d5ac510da98cee39a8d

CH-J Proprietary Software License 1.14 · کد منبع تحت CH-J Proprietary Software License 1.14 ارائه می‌شود. در دسترس بودن آن شرایط مجوز را تغییر نمی‌دهد و حق اضافه‌ای اعطا نمی‌کند.

SHA-256 بایگانی46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655
فایل‌ها