refactor: remove docker requirement of tuic#1345
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an in-process TUIC test server and test utilities, moves TUIC tests off Docker to local TCP tests, updates ChangesTUIC tests and dependency updates
Sequence DiagramsequenceDiagram
participant Test as Test Code
participant TSP as TuicServerProcess
participant Task as Tokio Task
participant Server as tuic-server
participant Channel as Oneshot Channel
Test->>TSP: start()
TSP->>TSP: bind UDP / build Config
TSP->>Task: spawn server task
Task->>Server: Server::init(ctx) -> local_addr
Server->>Channel: send port & readiness
Channel-->>TSP: readiness received
TSP-->>Test: return TuicServerProcess(handle, port)
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@clash-lib/src/proxy/tuic/mod.rs`:
- Around line 533-559: The test currently sets session.destination to
"127.0.0.1:9999" (where nothing listens) so failures can come from an
unreachable upstream; instead spawn a real local TcpListener and use its bound
address as the session.destination so the upstream is reachable, then proceed to
call handler.connect_stream(&session, resolver).await, accept the incoming
connection on the listener if any, and assert that the TUIC stream is
rejected/closed (i.e., that stream.write_all or stream.read_exact returns Err)
before any successful proxy I/O; update the code around Session construction and
the connect_stream/assertion to create the listener (e.g.,
tokio::net::TcpListener::bind("127.0.0.1:0").await), set destination from
listener.local_addr(), optionally spawn a task to accept the upstream
connection, and ensure the test cleans up the listener/task after the assertion.
In `@clash-lib/src/proxy/tuic/test_utils.rs`:
- Around line 14-15: The current alloc_port() implementation picks a free port
by creating and immediately dropping a temporary UdpSocket, introducing a TOCTOU
race when tuic-server later binds; change alloc_port() to bind a socket to 0
(e.g., UdpSocket::bind("127.0.0.1:0") or TcpListener::bind("127.0.0.1:0")), read
the actual assigned port from socket.local_addr().port(), and return that port
(keeping the socket alive until the server is spawned if necessary), or
alternatively modify the tuic-server startup path to accept port 0 and read the
bound port from the listener it creates; if keeping the original approach, add a
bounded retry loop around server bind (checking for EADDRINUSE) to retry a few
times before failing to avoid flakiness when alloc_port() selection races with
other processes.
- Around line 73-93: The test helper currently treats Server::init errors as
readiness by always sending on ready_tx and then ignoring the channel result;
change the ready signalling to propagate init failures: make ready_tx/ready_rx
send a Result<(), anyhow::Error> (or equivalent) instead of unit, and on Err(e)
from tuic_server::server::Server::init send Err(e) (or forward the error) rather
than sending Ok(()) so the caller can observe the failure; then when awaiting
the timeout on ready_rx, propagate the received Err to the caller (using ? or
map_err) instead of calling .ok(), so start()/the test returns Err when
initialization failed and does not report readiness for a dead server.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 098d516d-f27a-4929-b891-74be75e6d082
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
clash-lib/Cargo.tomlclash-lib/src/proxy/tuic/mod.rsclash-lib/src/proxy/tuic/test_utils.rsclash-lib/src/proxy/utils/test_utils/docker_utils/consts.rs
💤 Files with no reviewable changes (1)
- clash-lib/src/proxy/utils/test_utils/docker_utils/consts.rs
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
📊 Proxy Throughput ResultsShadowsocks
Trojan
VMess
VLESS
SOCKS5
AnyTLS
Hysteria2
TUIC
ShadowQUIC
SSH
Netem Tests (50 ms delay, 1% packet loss)Shadowsocks
Trojan
Hysteria2
TUIC
ShadowQUIC
Ran 34 variant(s) in parallel; each direction transfers the full payload. 🖥️ Test Environment
📎 View full workflow run and download artifacts Full test logDownload the |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/coverage.yml:
- Around line 36-39: The coverage workflow removed explicit NASM provisioning
but README.md/CLAUDE.md still declare NASM required for Windows builds; restore
an explicit NASM install step in .github/workflows/coverage.yml (near the
"Install cargo-llvm-cov" step) that runs only for the windows-latest job, or
alternatively update the docs to state that GitHub runners now include NASM;
locate the relevant job in coverage.yml around the "Install cargo-llvm-cov" step
and either insert a Windows-only NASM install action (e.g., choco/winget
installation) before build/coverage steps or change README.md/CLAUDE.md to
reflect the runner provisioning so the declared requirements match the workflow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 7d4d0947-98f7-4d56-8cbd-a1bc2d990bed
📒 Files selected for processing (1)
.github/workflows/coverage.yml
There was a problem hiding this comment.
🧹 Nitpick comments (2)
clash-lib/src/proxy/tuic/test_utils.rs (1)
60-74: Consider combining duplicate iteration patterns.Both
online_counterandtraffic_statsiterate overcfg.userswith identical structure. A single loop would be cleaner.♻️ Suggested refactor
- let mut online_counter = HashMap::new(); - for (user, _) in cfg.users.iter() { - online_counter - .insert(user.to_owned(), std::sync::atomic::AtomicUsize::new(0)); - } - let mut traffic_stats = HashMap::new(); - for (user, _) in cfg.users.iter() { - traffic_stats.insert( - user.to_owned(), - ( - std::sync::atomic::AtomicUsize::new(0), - std::sync::atomic::AtomicUsize::new(0), - ), - ); - } + let (online_counter, traffic_stats) = cfg + .users + .keys() + .map(|user| { + let counter = (user.to_owned(), std::sync::atomic::AtomicUsize::new(0)); + let stats = ( + user.to_owned(), + ( + std::sync::atomic::AtomicUsize::new(0), + std::sync::atomic::AtomicUsize::new(0), + ), + ); + (counter, stats) + }) + .unzip();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clash-lib/src/proxy/tuic/test_utils.rs` around lines 60 - 74, The code duplicates iteration over cfg.users to populate online_counter and traffic_stats; refactor by using a single loop over cfg.users and within that loop insert both online_counter.insert(user.to_owned(), AtomicUsize::new(0)) and traffic_stats.insert(user.to_owned(), (AtomicUsize::new(0), AtomicUsize::new(0))); reference the existing symbols online_counter, traffic_stats, and cfg.users and keep the same to_owned()/AtomicUsize::new(0) semantics to preserve behavior.clash-lib/src/proxy/tuic/mod.rs (1)
564-575: Arbitrary sleep makes test potentially slow and flaky.The 1-second sleep at line 567 is a fixed delay hoping the server processes auth rejection. This could be too short under load or unnecessarily slow in normal conditions.
Consider polling with a short timeout in a loop instead:
♻️ Suggested approach
if let Ok(mut stream) = result { let mut buf = [0u8; 5]; - // Give the server time to process auth and close - tokio::time::sleep(Duration::from_secs(1)).await; - let write_result = stream.write_all(b"hello").await; - let read_result = stream.read_exact(&mut buf).await; - assert!( - write_result.is_err() || read_result.is_err(), - "expected IO error after auth failure, but both read and write \ - succeeded" - ); + // Poll for failure with timeout instead of fixed sleep + let io_result = tokio::time::timeout(Duration::from_secs(5), async { + loop { + if stream.write_all(b"hello").await.is_err() { + return true; // write failed as expected + } + if stream.read_exact(&mut buf).await.is_err() { + return true; // read failed as expected + } + tokio::time::sleep(Duration::from_millis(50)).await; + } + }) + .await; + assert!( + io_result.is_ok(), + "expected IO error after auth failure within timeout" + ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clash-lib/src/proxy/tuic/mod.rs` around lines 564 - 575, The fixed 1s sleep before testing stream IO makes the test slow and flaky; replace the tokio::time::sleep(Duration::from_secs(1)).await in the block that handles Ok(mut stream) (the variable result/stream used with stream.write_all and stream.read_exact) with a short polling loop that repeatedly attempts a non-blocking/read-write probe (call the same stream.write_all and stream.read_exact) until either you observe the expected IO error or a small overall timeout elapses (use tokio::time::timeout or loop with tokio::time::sleep(Duration::from_millis(...)) between probes); fail the test if the timeout elapses without an IO error and keep the existing assert semantics once an error is observed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@clash-lib/src/proxy/tuic/mod.rs`:
- Around line 564-575: The fixed 1s sleep before testing stream IO makes the
test slow and flaky; replace the
tokio::time::sleep(Duration::from_secs(1)).await in the block that handles
Ok(mut stream) (the variable result/stream used with stream.write_all and
stream.read_exact) with a short polling loop that repeatedly attempts a
non-blocking/read-write probe (call the same stream.write_all and
stream.read_exact) until either you observe the expected IO error or a small
overall timeout elapses (use tokio::time::timeout or loop with
tokio::time::sleep(Duration::from_millis(...)) between probes); fail the test if
the timeout elapses without an IO error and keep the existing assert semantics
once an error is observed.
In `@clash-lib/src/proxy/tuic/test_utils.rs`:
- Around line 60-74: The code duplicates iteration over cfg.users to populate
online_counter and traffic_stats; refactor by using a single loop over cfg.users
and within that loop insert both online_counter.insert(user.to_owned(),
AtomicUsize::new(0)) and traffic_stats.insert(user.to_owned(),
(AtomicUsize::new(0), AtomicUsize::new(0))); reference the existing symbols
online_counter, traffic_stats, and cfg.users and keep the same
to_owned()/AtomicUsize::new(0) semantics to preserve behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 14c9eb89-3b23-424a-aaa9-350e3f6cd16f
📒 Files selected for processing (2)
clash-lib/src/proxy/tuic/mod.rsclash-lib/src/proxy/tuic/test_utils.rs
There was a problem hiding this comment.
🧹 Nitpick comments (1)
clash-lib/Cargo.toml (1)
176-176: Consider adding explicitrevvalues to TUIC git dependencies for self-contained manifests.Line 176 and Line 225 specify
tag = "v1.7.2"without explicitrevvalues. While Cargo.lock already pins the exact commit (18b74bcf11fe33caf9dcfc9e2d6685c5230a2e0a), addingrevdirectly in Cargo.toml makes the dependency specification more transparent and reduces reliance on Cargo.lock for reproducibility. This is especially valuable for supply-chain clarity in projects with frequent dependency updates.Also applies to: 225-225
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@clash-lib/Cargo.toml` at line 176, The TUIC git dependency entries (e.g., the tuic-core dependency declaration) currently specify tag = "v1.7.2" without an explicit rev; update each TUIC git dependency in Cargo.toml (the tuic-core entry at the shown locations and the other TUIC git entry around the later occurrence) to include rev = "18b74bcf11fe33caf9dcfc9e2d6685c5230a2e0a" (the commit from Cargo.lock) alongside tag = "v1.7.2" so the manifest is self-contained and reproducible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@clash-lib/Cargo.toml`:
- Line 176: The TUIC git dependency entries (e.g., the tuic-core dependency
declaration) currently specify tag = "v1.7.2" without an explicit rev; update
each TUIC git dependency in Cargo.toml (the tuic-core entry at the shown
locations and the other TUIC git entry around the later occurrence) to include
rev = "18b74bcf11fe33caf9dcfc9e2d6685c5230a2e0a" (the commit from Cargo.lock)
alongside tag = "v1.7.2" so the manifest is self-contained and reproducible.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b936b8f5-8796-4ac1-b55a-af719f3aaeb1
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (1)
clash-lib/Cargo.toml
|
is it overall better to run the server like this than using a docker container? i think docker is doing it pretty well to avoid crafting the server logic manually for each outbounds, don't see much benefits here replacing it |
It doesn't run on all platform, such as windows. I think using dep could test more cases, not just on linux which is the only platform supplies docker |
|
ok that's a fair point. we should have macOS support after this #1039 maybe we should enable them all and fix Windows support too if not working already |
# Conflicts: # Cargo.lock # clash-lib/Cargo.toml
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@clash-lib/Cargo.toml`:
- Line 185: The TUIC git dependencies in Cargo.toml (tuic-core and tuic-server)
currently track branch="main"; change both entries to use an explicit
rev="COMMIT_SHA" (the same commit SHA for both crates) instead of branch,
preserving existing features and optional flags, to make the dependency
deterministic; after updating the manifest regenerate the lockfile (cargo update
or equivalent) so Cargo.lock records the pinned rev rather than ?branch=main#...
.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 0adf3344-5fa2-4567-b049-6aace0449b93
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
clash-lib/Cargo.tomlclash-lib/src/proxy/tuic/mod.rsclash-lib/src/proxy/tuic/test_utils.rsclash-lib/src/proxy/utils/test_utils/docker_utils/consts.rs
💤 Files with no reviewable changes (3)
- clash-lib/src/proxy/utils/test_utils/docker_utils/consts.rs
- clash-lib/src/proxy/tuic/test_utils.rs
- clash-lib/src/proxy/tuic/mod.rs
…of-tuic # Conflicts: # Cargo.lock # clash-lib/Cargo.toml
The connection-chain test was hitting an external httpbin instance that is currently returning 502 Bad Gateway on CI (and live as I write this), causing the shadowsocks-feature api_tests job to fail across all platforms. Run a tiny local hyper HTTP/1 server bound to 127.0.0.1:0 and swap the upstream-domain rule for a DST-PORT match against that server's port — the `["DIRECT", "url-test", "test 🌏"]` chain still fires, but the test is now hermetic. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`cargo +nightly fmt --all -- --check` flagged three formatting issues in the previous commit: import ordering in api_tests.rs, a split `use` inside DripServer::start, and a wrapped `let body = Full::new(...)` line that fits on a single line. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`cababdcf "remove"` dropped the `IMAGE_TUIC` constant from `docker_utils/consts.rs`, but `e2e_throughput_tuic_bbr` in `clash-lib/src/proxy/tuic/mod.rs` (gated on `cfg(all(test, docker_test, throughput_test))`) still references it. That breaks the Proxy E2E Throughput job with `cannot find value IMAGE_TUIC in this scope`. The non-docker unit tests this PR adds do not use this constant, so re-add it under the same docker+throughput cfg the consumer is gated on. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Commit 6f7c359 ("feat: silently ignore unknown top-level/dns config fields; add --strict-config") landed a follow-up "chore: default --compatibility to false" that reverted clap's `ArgAction::Set` on the flag, so `--compatibility` is now a plain boolean switch. Passing `--compatibility=false` to the binary now errors with "unexpected value 'false' for '--compatibility' found; no more were expected", failing every clash-rs subprocess spawned by the E2E throughput tests. The flag's new default is already `false`, which is what the tests want, so just drop the arg. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`test_tuic_ping_pong_tcp{,_ipv6,_dual_stack}` reliably fail on cross
targets (aarch64-gnu/musl, armv7-gnu/musl, riscv64gc) with "Error: early
eof" — auth completes, then the QUIC stream gets reset mid-relay. The
same tests pass on every native target we run (Linux x86_64, macOS
aarch64, Windows). QUIC under qemu-user is unreliable: packet timing,
MTU discovery and timers all drift enough to race against TUIC's idle /
request timeouts.
Add `running_under_qemu_user()` to the TUIC test_utils — it asks the
kernel for `utsname.machine` and compares it to the binary's
compile-time `target_arch`. A mismatch means we're a foreign-arch binary
running through qemu-user (which is exactly the `cross test` setup).
Native runs match and proceed normally.
Plumb a `skip_under_qemu_user!` macro into the three failing tests so
they emit a one-line skip notice instead of flaking. `test_tuic_auth_failure`
keeps running everywhere — it doesn't depend on relay timing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
For environments where the command line is fixed (containers, init systems, GUI launchers) it's useful to flip on `--compatibility` from outside. Read `CLASH_RS_COMPATIBILITY_MODE` right after Cli::parse_from and treat `1` or `true` (case-insensitive, trimmed) as equivalent to passing `--compatibility`. The CLI flag still takes precedence when already set, and any other value (including `0`, `false`, empty) is a no-op. Add a small `env_truthy` helper with its own unit tests so the accept / reject sets are pinned down. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous `if !cli.compatibility && env_truthy(...)` form was semantically OR but read at-a-glance like an AND. Replace with the direct expression `cli.compatibility = cli.compatibility || env_truthy(...)` to make the precedence obvious. Behaviour unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rver" Remove DripServer and restore the test's original https://httpbin.yba.dev/drip request, per request. clash-lib/tests/api_tests.rs and clash-lib/tests/common/mod.rs are now identical to master for the two files touched by the drip work. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous runtime detector compared the binary's compile-time target_arch to \`utsname.machine\`, expecting a mismatch under qemu-user. qemu-user fakes the uname syscall to return the *target* architecture, so the detector silently returned false and the tests still ran (and flaked) on aarch64 / armv7 / riscv64 cross builds. Replace with a much simpler compile-time gate: on non-x86_64 Linux, mark each ping-pong test \`#[ignore]\`. All such targets in CI are cross-built and run under qemu-user; the assumption holds until a native non-x86 Linux runner is added (at which point this can be revisited). Native Linux x86_64, macOS aarch64, and Windows x86_64 continue to cover the tests. Also drop the now-unused running_under_qemu_user / arch_matches / skip_under_qemu_user! macro from test_utils.rs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A reusable predicate for tests that need to bail out under qemu-user. The rule: \`target_os = "linux"\` AND \`target_arch\` is neither \`x86_64\` nor \`x86\` (i686) — every such target in our CI matrix is produced by \`cross\` and executed under qemu-user, where QUIC and other timing-sensitive paths drift enough to flake. Implemented as a \`pub const fn\` so the value folds at compile time and can be used in \`const\` contexts (e.g. \`const _: () = assert!(...)\`). Pure \`cfg!\` body — no syscalls, no env lookups, no false negatives from qemu's faked \`utsname\`. Add two unit tests: - \`predicate_matches_target_arch\` pins the truth table. - \`usable_in_const_context\` confirms it folds at compile time. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
\`cfg_attr\` only accepts cfg-predicate syntax, not function calls — even a const fn — so the existing helper couldn't shorten the long \`all(target_os = "linux", not(any(target_arch = "x86_64", target_arch = "x86")))\` predicate that the three TUIC ping-pong tests repeated. Have \`build.rs\` emit \`--cfg likely_qemu_emulated\` when the same condition is true. Now tests can write \`#[cfg_attr(likely_qemu_emulated, ignore = "…")]\` directly. Rewrite \`likely_qemu_emulated()\` as \`cfg!(likely_qemu_emulated)\` so the const fn and the cfg flag share a single source of truth (build.rs). Apply the new cfg to all three TUIC tests and update the unit test to assert the build.rs emit matches the inline predicate. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per request: - Shorten the rustc-cfg flag name: \`likely_qemu_emulated\` → \`qemu_emulated\`. - Drop the \`pub const fn likely_qemu_emulated()\` helper. The cfg flag alone covers every call site we have (\`#[cfg_attr(qemu_emulated, …)]\`); the const fn was just an alias. Replace the two helper unit tests with a single sanity check that \`cfg!(qemu_emulated)\` matches build.rs's emit rule, so the flag and the docs can't drift. Also rename the cfg in the three TUIC \`#[cfg_attr]\` sites. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary by CodeRabbit
Tests
Chores