Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Test Pipeline

The test command probes each selected config across one or more stages (ICMP, TCP, real-delay, download, upload) and persists a per-config connection_tests row plus a connection_test_runs row that groups the batch.

High-Level Flow

flowchart TD
    classDef cli    fill:#1a2744,stroke:#4a9eff,color:#e6edf3
    classDef app    fill:#2a1a3a,stroke:#b070df,color:#e6edf3
    classDef engine fill:#2e1a1a,stroke:#df6060,color:#e6edf3
    classDef probe  fill:#2a2a1a,stroke:#c0df5b,color:#e6edf3
    classDef store  fill:#1a2e2e,stroke:#5bcfdf,color:#e6edf3

    START["CLI: xrat test"]:::cli
    RESOLVE["resolve_test_settings()<br/>settings/resolve.rs"]:::app
    LOAD["load target configs<br/>db/repository/configs/"]:::store
    EXEC["run for each config<br/>bulk/single.rs"]:::app
    PROBE["generate probe config<br/>spawn Xray process"]:::engine
    STAGES["run enabled stages<br/>sequentially"]:::probe
    KILL["kill probe process"]:::engine
    SAVE["persist results<br/>db/repository/connection_tests/"]:::store
    OUT["format & print<br/>output/print.rs"]:::app

    START --> RESOLVE --> LOAD --> EXEC --> PROBE --> STAGES --> KILL --> SAVE --> OUT

Probers

src/prober/ is the leaf-level measurement crate. Each prober is a small async function that returns a *Result struct plus a FailureKind on failure. The combined TestResult is the only thing the rest of the command touches.

#![allow(unused)]
fn main() {
pub use download::{DownloadResult, download_speed_check};
pub use icmp::{IcmpResult, icmp_ping};
pub use real_delay::{RealDelayResult, real_delay_check};
pub use tcp::{TcpResult, tcp_check};
pub use upload::{UploadResult, upload_speed_check};

pub struct TestResult {
    pub icmp_ok: bool,
    pub icmp_ms: Option<u32>,
    pub tcp_ok: bool,
    pub tcp_ms: Option<u32>,
    pub real_delay_ok: bool,
    pub real_delay_ms: Option<u32>,
    pub download_ok: bool,
    pub download_mbps: Option<f64>,
    pub upload_ok: bool,
    pub upload_mbps: Option<f64>,
    pub ttfb_ms: Option<u32>,
    pub http_status: Option<u16>,
    pub dial_endpoint_ip: Option<String>,
    pub dial_endpoint_location: Option<String>,
    pub dial_endpoint_country: Option<String>,
    pub dial_endpoint_asn: Option<String>,
    pub dial_endpoint_geoip_source: Option<String>,
    pub dial_endpoint_fronting: Option<String>,
    pub failure_kind: Option<FailureKind>,
    pub failure_reason: Option<String>,
}
}

Prober Source Layout

src/prober/
├── icmp/
│   ├── mod.rs          — icmp_ping, ping_with_system_command
│   └── parsing.rs      — parse_ping_latency, classify_ping_failure
├── tcp/
│   ├── check.rs        — tcp_check
│   ├── classify.rs     — classify_dns_error, classify_tcp_error
│   ├── model.rs        — TcpResult
│   ├── mod.rs
│   ├── errors.rs
│   └── tests.rs
├── real_delay/
│   ├── check/
│   │   ├── execute.rs  — proxied HTTP latency through Xray
│   │   ├── model.rs    — RealDelayResult
│   │   ├── mod.rs
│   │   ├── port.rs     — inbound port detection
│   │   └── request.rs  — request execution
│   ├── classify.rs
│   └── mod.rs
├── download/
│   ├── check/
│   │   ├── proxied.rs  — proxied download
│   │   ├── result.rs   — throughput calculation
│   │   └── mod.rs
│   ├── classify.rs
│   └── mod.rs
└── upload/
    ├── classify.rs     — classify_request_error, classify_xray_error
    ├── mod.rs          — upload_speed_check
    └── request.rs      — make_proxied_upload

ICMP, TCP, and upload keep their logic in a single module + flat classify/parsing files; real_delay and download push the proxied network code one level deeper into check/.

Settings Resolution

The persisted TestingSettings (in app/config/testing/types.rs) is the “config file + defaults” form. settings/resolve.rs::resolve_test_settings merges that with the CLI flags into a ResolvedTestSettings value that the executor consumes.

#![allow(unused)]
fn main() {
pub struct TestingSettings {
    pub concurrency: i32,
    pub order: Vec<ConnectionTestStage>,
    pub failure_policy: TestFailurePolicy,
    pub real_delay: RealDelayTestSettings,
    pub icmp: IcmpTestSettings,
    pub download: DownloadTestSettings,
    pub tcp: TcpTestSettings,
    pub geoip: GeoIpTestSettings,
}

pub enum ConnectionTestStage {
    Icmp,
    RealDelay,
    Download,
}

pub enum TestFailurePolicy {
    Continue,
    SkipRemaining,
    MarkFailed,
}
}

TestFailurePolicy::halts_after_failure() returns true for SkipRemaining and MarkFailed; the executor uses this to decide whether to keep going after a stage failure.

Resolved Settings

#![allow(unused)]
fn main() {
pub(crate) struct ResolvedTestSettings {
    pub(crate) stage_order: Vec<ConnectionTestStage>,
    pub(crate) failure_policy: TestFailurePolicy,
    pub(crate) real_delay_url: String,
    pub(crate) download_url: String,
    pub(crate) upload_url: Option<String>,         // upload only runs if Some
    pub(crate) xray_binary_path: PathBuf,
    pub(crate) icmp_timeout: Duration,
    pub(crate) tcp_timeout: Duration,
    pub(crate) xray_startup_timeout: Duration,
    pub(crate) real_delay_timeout: Duration,
    pub(crate) download_timeout: Duration,
    pub(crate) upload_timeout: Duration,
    pub(crate) upload_payload_bytes: usize,
    pub(crate) run_icmp: bool,
    pub(crate) run_tcp: bool,
    pub(crate) run_real_delay: bool,
    pub(crate) run_download: bool,
    pub(crate) run_upload: bool,
    pub(crate) concurrency: i32,
    pub(crate) geoip_enabled: bool,
    pub(crate) geoip_country_path: PathBuf,
    pub(crate) geoip_city_path: PathBuf,
    pub(crate) geoip_asn_path: PathBuf,
}
}

Note: upload_url is Option<String> and the upload stage is skipped when it is NoneConnectionTestStage itself has no Upload variant.

Failure Classification

#![allow(unused)]
fn main() {
pub enum FailureKind {
    Dns,
    Timeout,
    Refused,
    Unreachable,
    PermissionDenied,
    Tls,
    Auth,
    Process,
    Proxy,
    Unknown,
}
}

as_str() is the canonical snake-case form stored in connection_tests.failure_kind. Classifiers live next to each prober (icmp/parsing.rs::classify_ping_failure, tcp/classify.rs::classify_dns_error/classify_tcp_error, upload/classify.rs::classify_request_error/classify_xray_error).

flowchart LR
    classDef input fill:#1a2744,stroke:#4a9eff,color:#e6edf3
    classDef icmp  fill:#2e2a1a,stroke:#dfba5b,color:#e6edf3
    classDef tcp   fill:#2a1a3a,stroke:#b070df,color:#e6edf3
    classDef http  fill:#2e1a1a,stroke:#df6060,color:#e6edf3

    FAIL["test failure"]:::input
    CAT{"prober"}

    ICMP["dns · timeout · permission_denied<br/>unreachable · unknown"]:::icmp
    TCP["dns · timeout · refused<br/>unreachable · permission_denied · unknown"]:::tcp
    RD["process · timeout · tls<br/>auth · proxy · unknown"]:::http
    TH["process · timeout · tls<br/>auth · proxy · unknown"]:::http

    FAIL --> CAT
    CAT -- "ICMP"            --> ICMP
    CAT -- "TCP"             --> TCP
    CAT -- "RealDelay"       --> RD
    CAT -- "Download/Upload" --> TH

Stage Execution

Per config, the executor walks stage_order and runs the matching run_*_stage function (stages/throughput.rs etc.) into a single TestResult. ICMP and TCP run without spinning up Xray; real-delay, download, and upload each generate a probe config and spawn a short-lived Xray process via xray::XrayProcess::spawn_with_binary.

flowchart LR
    classDef direct fill:#2e2a1a,stroke:#dfba5b,color:#e6edf3
    classDef proxy  fill:#2e1a1a,stroke:#df6060,color:#e6edf3
    classDef opt    fill:#1a2c3a,stroke:#5b8def,color:#e6edf3

    ICMP["ICMP<br/>(direct)"]:::direct
    TCP["TCP<br/>(direct)"]:::direct
    REAL["Real Delay<br/>(via Xray)"]:::proxy
    DL["Download<br/>(via Xray)"]:::proxy
    UL["Upload<br/>(via Xray, optional)"]:::opt

    ICMP --> TCP --> REAL --> DL -.-> UL

Stages marked (direct) probe the remote endpoint without a proxy process. Stages marked (via Xray) spawn a short-lived Xray probe instance on a random local port. Upload runs only when upload_url is configured.

Persistence

db/repository/connection_tests/ writes one row per config with the fields in ConnectionTestRecord, and connection_test_runs records the batch metadata (kind, created_at). The bulk executor groups results into a single run id so callers can paginate by batch.

Output Formatting

output/print.rs and output/format.rs produce the per-row and tabular output for the CLI. The TUI reuses the same in-process executors through app::commands::test::bulk::* and does not shell out to a child xrat test process.