Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
toolchain: ${{ env.RUST_VERSION }}
components: clippy,rustfmt

- name: Cache Rust build
uses: Swatinem/rust-cache@v2

- name: Rust format
run: cargo fmt --check

Expand All @@ -51,6 +54,9 @@ jobs:
with:
toolchain: ${{ env.RUST_VERSION }}

- name: Cache Rust build
uses: Swatinem/rust-cache@v2

- name: Install protoc (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
Expand All @@ -64,4 +70,4 @@ jobs:
run: choco install protoc -y

- name: Rust integration tests against Rust binary
run: cargo test --locked --all-features --test cli --test formatting --test grpc --test http --test install --test network --test terminal --test update --test websocket -- --test-threads=1
run: cargo test --locked --all-features --test cli --test formatting --test grpc --test har --test http --test install --test network --test terminal --test update --test websocket -- --test-threads=2
11 changes: 6 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ Full CI-equivalent before PRs, shared transport/request/response changes, or unc
cargo fmt --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked --all-features --lib --bins
cargo test --locked --all-features --test cli --test formatting --test grpc --test http --test install --test network --test terminal --test update --test websocket
cargo test --locked --all-features --test cli --test formatting --test grpc --test har --test http --test install --test network --test terminal --test update --test websocket -- --test-threads=2
```

The integration test suite uses `TcpListener::bind("127.0.0.1:0")` and
`UdpSocket::bind("127.0.0.1:0")` for all test servers, so tests are safe to run in
parallel. If transient "Connection refused" errors occur from port reuse races, add
`-- --test-threads=1` to force sequential execution. The `TestServer` and
`H3TestServer` use `mpsc` channels instead of polling for request notification.
`UdpSocket::bind("127.0.0.1:0")` for all test servers. CI uses two test threads to
speed up the suite without overloading timing-sensitive socket tests. If transient
"Connection refused" or connection-reset errors occur from port reuse races, use
`-- --test-threads=1` to diagnose sequentially. The `TestServer` and `H3TestServer`
use `mpsc` channels instead of polling for request notification.

Docs-only changes: skip Cargo unless examples/generated CLI output changed; format changed docs only:

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,4 @@ predicates = "=3.1.4"
rcgen = { version = "=0.14.8", features = ["aws_lc_rs"] }
sha1 = "=0.11.0"
tempfile = "=3.27.0"
wait-timeout = "=0.2.1"
71 changes: 38 additions & 33 deletions tests/support/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::time::{Duration, Instant};

use tempfile::TempDir;
use url::Url;
use wait_timeout::ChildExt;

#[cfg(unix)]
use std::os::fd::{FromRawFd, RawFd};
Expand Down Expand Up @@ -113,30 +114,41 @@ pub(crate) fn run_fetch_once(opts: FetchOpts, args: &[&str]) -> FetchOutput {
let mut stdin = child.stdin.take().expect("child stdin");
stdin.write_all(input.as_bytes()).expect("write stdin");
}
let start = Instant::now();
loop {
if child.try_wait().expect("poll fetch").is_some() {
break;
}
if start.elapsed() > Duration::from_secs(15) {
let _ = child.kill();
let out = child.wait_with_output().expect("wait killed fetch");
return FetchOutput {
status: out.status,
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
stderr: format!(
"{}\nfetch test harness timeout after 30s",
String::from_utf8_lossy(&out.stderr)
),
};
}
thread::sleep(Duration::from_millis(10));

// Drain both pipes while the child runs so a verbose command cannot block on
// a full pipe. wait_timeout avoids adding up to 10ms of polling latency to
// every short-lived CLI invocation in the integration suite.
let mut stdout = child.stdout.take().expect("child stdout");
let stdout_reader = thread::spawn(move || {
let mut output = Vec::new();
stdout.read_to_end(&mut output).expect("read fetch stdout");
output
});
let mut stderr = child.stderr.take().expect("child stderr");
let stderr_reader = thread::spawn(move || {
let mut output = Vec::new();
stderr.read_to_end(&mut output).expect("read fetch stderr");
output
});

let timed_out = child
.wait_timeout(Duration::from_secs(15))
.expect("wait for fetch")
.is_none();
if timed_out {
let _ = child.kill();
}
let status = child.wait().expect("wait fetch");
let stdout = stdout_reader.join().expect("join fetch stdout reader");
let stderr = stderr_reader.join().expect("join fetch stderr reader");
let mut stderr = String::from_utf8_lossy(&stderr).into_owned();
if timed_out {
stderr.push_str("\nfetch test harness timeout after 15s");
}
let out = child.wait_with_output().expect("wait fetch");
FetchOutput {
status: out.status,
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
status,
stdout: String::from_utf8_lossy(&stdout).into_owned(),
stderr,
}
}

Expand Down Expand Up @@ -286,17 +298,10 @@ pub(crate) fn wait_child(
child: &mut std::process::Child,
timeout: Duration,
) -> Option<std::io::Result<ExitStatus>> {
let start = Instant::now();
loop {
match child.try_wait() {
Ok(Some(status)) => return Some(Ok(status)),
Ok(None) => {}
Err(err) => return Some(Err(err)),
}
if start.elapsed() > timeout {
return None;
}
thread::sleep(Duration::from_millis(10));
match child.wait_timeout(timeout) {
Ok(Some(status)) => Some(Ok(status)),
Ok(None) => None,
Err(err) => Some(Err(err)),
}
}

Expand Down
Loading