Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6d64107
fix(test): fix all unit tests for macOS Apple Silicon
litcc Mar 1, 2026
6504d5c
refactor(docker-utils): simplify container_ip and gateway_ip methods
litcc Mar 2, 2026
1cfb691
Merge branch 'master' into fix/unit-tests
ibigbug Mar 3, 2026
83479d7
feat(docker-test): support remote Docker connections via DOCKER_HOST
litcc Mar 3, 2026
61a3e63
Add GIT_CURL_VERBOSE and GIT_TRACE to CI config
Itsusinn Mar 3, 2026
b20b145
Update ci.yml
Itsusinn Mar 3, 2026
de572a4
feat(docker-test): add docker_gateway_ip method and update tests to u…
litcc Mar 3, 2026
ad1f0e8
Merge branch 'fix/unit-tests' into fix/unit-tests2
litcc Mar 3, 2026
bf88417
Update ci.yml
Itsusinn Mar 3, 2026
cdce863
fix(docker-test): docker remote mounts
litcc Mar 4, 2026
cf2501f
fix(docker-test): update container_ip method
litcc Mar 5, 2026
7aa059d
fix(docker-test): bug
litcc Mar 5, 2026
8300798
fix(docker-test): bugs and unit tests
litcc Mar 5, 2026
cd27e66
fix(docker-test): code warnings
litcc Mar 5, 2026
5fb33d6
fix(docker-test): bugs and unit tests
litcc Mar 5, 2026
7c5785f
fix(docker-test): bug
litcc Mar 5, 2026
2bd08b1
Merge branch 'master' into fix/unit-tests2
litcc Mar 7, 2026
298710c
fix(test_hysteria): resolve Windows-specific test failures
litcc Mar 8, 2026
bfa5c64
fix(test): resolve DNS handler test port conflicts
litcc Mar 8, 2026
fd7c3a7
Merge branch 'fix/unit-tests2' into fix/unit-tests
litcc Mar 8, 2026
9735648
fix(docker-test): bug
litcc Mar 9, 2026
a4ed931
fix(docker-test): bug
litcc Mar 10, 2026
e0a9e61
fix(docker-test): bug
litcc Mar 10, 2026
b7549ff
fix(docker-test): bug
litcc Mar 10, 2026
4f009cf
fix(clippy): resolve clippy warnings and improve code quality
litcc Mar 10, 2026
e807fed
Merge branch 'master' into fix/unit-tests
litcc Mar 10, 2026
1d2e1d7
fix(test): some writing issues
litcc Mar 11, 2026
8e2601b
fix(clippy): some
litcc Mar 11, 2026
fdae7b8
fix(test): test_connections_returns_proxy_chain_names
litcc Mar 12, 2026
4241e8f
Merge branch 'master' into fix/unit-tests-3
litcc Mar 12, 2026
a17b40b
fix(deps): Handling merged dependency issues
litcc Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 262 additions & 7 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion clash-bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ sentry = { version = "0.46", default-features = false, features = ["backtrace",
human-panic = "2.0"


aws-lc-rs = { version = "1", optional = true, default-features = false }
aws-lc-rs = { version = "1.16", optional = true, default-features = false }
28 changes: 28 additions & 0 deletions clash-bin/tests/data/config/socks5-auth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"log": {
"loglevel": "debug"
},
"inbounds": [
{
"port": 10002,
"listen": "0.0.0.0",
"protocol": "socks",
"settings": {
"auth": "password",
"accounts": [
{
"user": "user",
"pass": "password"
}
],
"udp": true,
"ip": "0.0.0.0"
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
]
}
22 changes: 22 additions & 0 deletions clash-bin/tests/data/config/socks5-noauth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"log": {
"loglevel": "debug"
},
"inbounds": [
{
"port": 10002,
"listen": "0.0.0.0",
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true,
"ip": "0.0.0.0"
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
]
}
3 changes: 2 additions & 1 deletion clash-bin/tests/data/config/tuic.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ zero_rtt_handshake = false
dual_stack = false

acl = '''
direct localhost
direct 0.0.0.0/0
direct ::/0
'''

[users]
Expand Down
94 changes: 58 additions & 36 deletions clash-dns/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ mod tests {
tls::{self, global_root_store},
};
use futures::FutureExt;
use hickory_client::client::{self, Client, ClientHandle};
use hickory_client::client::{Client, ClientHandle};
use hickory_proto::{
h2::HttpsClientStreamBuilder,
h3::H3ClientStreamBuilder,
Expand All @@ -374,18 +374,11 @@ mod tests {
};
use rustls::ClientConfig;
use std::{sync::Arc, time::Duration};
use tokio::task::JoinHandle;
mod addr {
use std::net::{IpAddr, Ipv4Addr, SocketAddr};

const LOCAL: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
use tokio::{
net::{TcpListener, UdpSocket},
task::JoinHandle,
};

pub(super) const UDP: SocketAddr = SocketAddr::new(LOCAL, 53553);
pub(super) const TCP: SocketAddr = SocketAddr::new(LOCAL, 53554);
pub(super) const DOT: SocketAddr = SocketAddr::new(LOCAL, 53555);
pub(super) const DOH: SocketAddr = SocketAddr::new(LOCAL, 53556);
pub(super) const DOH3: SocketAddr = SocketAddr::new(LOCAL, 53557);
}
async fn send_query(client: &mut Client) -> anyhow::Result<()> {
let name = Name::from_ascii("www.example.com.").unwrap();

Expand Down Expand Up @@ -441,22 +434,52 @@ mod tests {
.boxed()
});

// Bind to port 0 to get OS-assigned available ports
let udp_sock = UdpSocket::bind("127.0.0.1:0").await?;
let udp_addr = udp_sock.local_addr()?;
drop(udp_sock);

let tcp_sock = TcpListener::bind("127.0.0.1:0").await?;
let tcp_addr = tcp_sock.local_addr()?;
drop(tcp_sock);

let dot_sock = TcpListener::bind("127.0.0.1:0").await?;
let dot_addr = dot_sock.local_addr()?;
drop(dot_sock);

let doh_sock = TcpListener::bind("127.0.0.1:0").await?;
let doh_addr = doh_sock.local_addr()?;
drop(doh_sock);

let doh3_sock = UdpSocket::bind("127.0.0.1:0").await?;
let doh3_addr = doh3_sock.local_addr()?;
drop(doh3_sock);
Comment on lines +437 to +456
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test reserves ephemeral ports by binding to 127.0.0.1:0, reading local_addr(), and then immediately dropping the sockets before starting the real listeners. This introduces a race where another process/test can grab the port in between, leading to flaky failures. Prefer letting get_dns_listener bind to port 0 itself (and then reading back the chosen ports), or keep the sockets/listeners open and pass them into the server if the API allows.

Copilot uses AI. Check for mistakes.

eprintln!(
"Test using ports - UDP:{}, TCP:{}, DoT:{}, DoH:{}, DoH3:{}",
udp_addr.port(),
tcp_addr.port(),
dot_addr.port(),
doh_addr.port(),
doh3_addr.port()
);

let cfg = DNSListenAddr {
udp: Some(addr::UDP),
tcp: Some(addr::TCP),
udp: Some(udp_addr),
tcp: Some(tcp_addr),
dot: Some(DoTConfig {
addr: addr::DOT,
addr: dot_addr,
ca_key: None,
ca_cert: None,
}),
doh: Some(DoHConfig {
addr: addr::DOH,
addr: doh_addr,
hostname: Some("dns.example.com".to_string()),
ca_key: None,
ca_cert: None,
}),
doh3: Some(DoH3Config {
addr: addr::DOH3,
addr: doh3_addr,
hostname: Some("dns.example.com".to_string()),
ca_key: None,
ca_cert: None,
Expand All @@ -473,18 +496,21 @@ mod tests {
Ok(())
});

// Wait for servers to start
tokio::time::sleep(Duration::from_millis(100)).await;

let stream =
UdpClientStream::builder(addr::UDP, TokioRuntimeProvider::new()).build();
UdpClientStream::builder(udp_addr, TokioRuntimeProvider::new()).build();

let (mut client, handle) = client::Client::connect(stream).await?;
let (mut client, handle) = Client::connect(stream).await?;
tokio::spawn(handle);

send_query(&mut client).await?;

let (stream, sender) =
TcpClientStream::new(addr::TCP, None, None, TokioRuntimeProvider::new());
TcpClientStream::new(tcp_addr, None, None, TokioRuntimeProvider::new());

let (mut client, handle) = client::Client::new(stream, sender, None).await?;
let (mut client, handle) = Client::new(stream, sender, None).await?;
tokio::spawn(handle);

send_query(&mut client).await?;
Expand All @@ -498,22 +524,18 @@ mod tests {
.set_certificate_verifier(Arc::new(tls::DummyTlsVerifier::new()));

let (stream, sender) = tls_client_connect(
addr::DOT,
dot_addr,
"dns.example.com".to_owned(),
Arc::new(tls_config),
TokioRuntimeProvider::new(),
);

let (mut client, handle) = client::Client::with_timeout(
stream,
sender,
Duration::from_secs(5),
None,
)
.await
.inspect_err(|e| {
assert!(false, "Failed to connect to DoT server: {}", e);
})?;
let (mut client, handle) =
Client::with_timeout(stream, sender, Duration::from_secs(5), None)
.await
.inspect_err(|e| {
assert!(false, "Failed to connect to DoT server: {}", e);
})?;
tokio::spawn(handle);

send_query(&mut client).await?;
Expand All @@ -532,12 +554,12 @@ mod tests {
TokioRuntimeProvider::new(),
)
.build(
addr::DOH,
doh_addr,
"dns.example.com".to_owned(),
"/dns-query".to_owned(),
);

let (mut client, handle) = client::Client::connect(stream).await?;
let (mut client, handle) = Client::connect(stream).await?;
tokio::spawn(handle);

send_query(&mut client).await?;
Expand All @@ -555,12 +577,12 @@ mod tests {
.crypto_config(tls_config)
.clone()
.build(
addr::DOH3,
doh3_addr,
"dns.example.com".to_owned(),
"/dns-query".to_owned(),
);

let (mut client, handle) = client::Client::connect(stream).await?;
let (mut client, handle) = Client::connect(stream).await?;
tokio::spawn(handle);

send_query(&mut client).await?;
Expand Down
5 changes: 4 additions & 1 deletion clash-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ criterion = { version = "0.8", features = ["html_reports", "async_tokio"], optio
memory-stats = "1.0.0"

# ssh
russh = { version = "0.56", default-features = false, features = ["async-trait"], optional = true }
russh = { version = "0.56", default-features = false, features = ["async-trait","rsa"], optional = true }
dirs = { version = "6.0", optional = true }
totp-rs = { version = "^5.7", features = ["serde_support"], optional = true }

Expand All @@ -202,13 +202,16 @@ mockall = "0.14.0"
tokio-test = "0.4.5"
axum-macros = "0.5.0"
bollard = "0.20"
tar = "0.4.44"
serial_test = "3.3"
env_logger = "0.11"
# donnot change the version, russh is not compatible with the latest version of rand_core
rand_chacha = "=0.3"
httpmock = "0.8.2" # TODO replace with wiremock
tracing-test = "0.2"
http-body-util = "0.1"
reqwest = { version = "0.13", features = ["socks"] }
sysinfo = { version = "0.38", features = ["network"]}

[build-dependencies]
prost-build = "0.14"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,10 @@ mod tests {
mock_vehicle.expect_read().returning(|| {
Ok(r#"
proxies:
- name: "ss"
type: ss
- name: "socks5"
type: socks5
server: localhost
port: 8388
cipher: aes-256-gcm
password: "password"
port: 1080
udp: true
"#
.as_bytes()
Expand Down
59 changes: 32 additions & 27 deletions clash-lib/src/proxy/group/relay/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,18 +208,20 @@ mod tests {

use tokio::sync::RwLock;

use crate::proxy::{
mocks::MockDummyProxyProvider,
utils::test_utils::{
Suite,
consts::*,
docker_runner::{DockerTestRunner, DockerTestRunnerBuilder},
run_test_suites_and_cleanup,
use super::*;
use crate::{
proxy::{
mocks::MockDummyProxyProvider,
utils::test_utils::{
Suite,
consts::*,
docker_runner::{DockerTestRunner, DockerTestRunnerBuilder},
run_test_suites_and_cleanup,
},
},
tests::initialize,
};

use super::*;

const PASSWORD: &str = "FzcLbKs2dY9mhL";
const CIPHER: &str = "aes-256-gcm";

Expand All @@ -236,17 +238,24 @@ mod tests {
#[tokio::test]
#[serial_test::serial]
async fn test_relay_1() -> anyhow::Result<()> {
initialize();
let port = 10002;
let container = get_ss_runner(port).await?;

let container_ip = container.container_ip();

debug!("container ip: {:?}", container_ip);
let ss_opts = crate::proxy::shadowsocks::outbound::HandlerOptions {
name: "test-ss".to_owned(),
common_opts: Default::default(),
server: LOCAL_ADDR.to_owned(),
port: 10002,
server: container_ip.unwrap_or(LOCAL_ADDR.to_owned()),
port,
password: PASSWORD.to_owned(),
cipher: CIPHER.to_owned(),
plugin: Default::default(),
udp: false,
};
let port = ss_opts.port;

let ss_handler: AnyOutboundHandler =
Arc::new(crate::proxy::shadowsocks::outbound::Handler::new(ss_opts))
as _;
Expand All @@ -262,28 +271,29 @@ mod tests {

let handler =
Handler::new(Default::default(), vec![Arc::new(RwLock::new(provider))]);
run_test_suites_and_cleanup(
handler,
get_ss_runner(port).await?,
Suite::all(),
)
.await
run_test_suites_and_cleanup(handler, container, Suite::all()).await
}

#[tokio::test]
#[serial_test::serial]
async fn test_relay_2() -> anyhow::Result<()> {
initialize();
let port = 10002;
let container = get_ss_runner(port).await?;

let container_ip = container.container_ip();

let ss_opts = crate::proxy::shadowsocks::outbound::HandlerOptions {
name: "test-ss".to_owned(),
common_opts: Default::default(),
server: LOCAL_ADDR.to_owned(),
port: 10002,
server: container_ip.unwrap_or(LOCAL_ADDR.to_owned()),
port,
password: PASSWORD.to_owned(),
cipher: CIPHER.to_owned(),
plugin: Default::default(),
udp: false,
};
let port = ss_opts.port;

let ss_handler: AnyOutboundHandler =
Arc::new(crate::proxy::shadowsocks::outbound::Handler::new(ss_opts))
as _;
Expand All @@ -299,11 +309,6 @@ mod tests {

let handler =
Handler::new(Default::default(), vec![Arc::new(RwLock::new(provider))]);
run_test_suites_and_cleanup(
handler,
get_ss_runner(port).await?,
Suite::all(),
)
.await
run_test_suites_and_cleanup(handler, container, Suite::all()).await
}
}
Loading
Loading