Skip to content

Commit b74f3b1

Browse files
authored
ci(fix): dynamically set ports in proxy integration tests (#11009)
### Description Tests were getting stuck because they were contending for ports. Makes it so that the tests: - Dynamically select ports - Run serially It's probable that they don't _need_ to run serially, but doing this since it won't hurt anything and can have more peace of mind. ### Testing Instructions CI
1 parent f3c735b commit b74f3b1

File tree

3 files changed

+156
-84
lines changed

3 files changed

+156
-84
lines changed

Cargo.lock

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turborepo-microfrontends-proxy/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ turborepo-microfrontends = { path = "../turborepo-microfrontends" }
2424
url = "2.2.2"
2525

2626
[dev-dependencies]
27+
serial_test = "3.0"
2728
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }

crates/turborepo-microfrontends-proxy/tests/integration_test.rs

Lines changed: 114 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{net::SocketAddr, time::Duration};
1+
use std::time::Duration;
22

33
use http_body_util::{BodyExt, Full};
44
use hyper::{
@@ -7,58 +7,69 @@ use hyper::{
77
service::service_fn,
88
};
99
use hyper_util::{client::legacy::Client, rt::TokioIo};
10+
use serial_test::serial;
1011
use tokio::net::TcpListener;
1112
use turborepo_microfrontends::Config;
1213
use turborepo_microfrontends_proxy::{ProxyServer, Router};
1314

14-
const WEBSOCKET_CLOSE_DELAY: Duration = Duration::from_millis(100);
15-
1615
#[tokio::test]
16+
#[serial]
1717
async fn test_port_availability_check_ipv4() {
18-
let config_json = r#"{
19-
"options": {
20-
"localProxyPort": 9999
21-
},
22-
"applications": {
23-
"web": {
24-
"development": {
25-
"local": { "port": 3000 }
26-
}
27-
}
28-
}
29-
}"#;
18+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
19+
let port = listener.local_addr().unwrap().port();
3020

31-
let config = Config::from_str(config_json, "test.json").unwrap();
32-
let server = ProxyServer::new(config.clone()).unwrap();
21+
let config_json = format!(
22+
r#"{{
23+
"options": {{
24+
"localProxyPort": {port}
25+
}},
26+
"applications": {{
27+
"web": {{
28+
"development": {{
29+
"local": {{ "port": 3000 }}
30+
}}
31+
}}
32+
}}
33+
}}"#
34+
);
3335

34-
let _listener = TcpListener::bind("127.0.0.1:9999").await.unwrap();
36+
let config = Config::from_str(&config_json, "test.json").unwrap();
37+
let server = ProxyServer::new(config.clone()).unwrap();
3538

3639
let result = server.check_port_available().await;
3740
assert!(!result, "Port should not be available when already bound");
41+
42+
drop(listener);
3843
}
3944

4045
#[tokio::test]
46+
#[serial]
4147
async fn test_port_availability_check_ipv6() {
42-
let config_json = r#"{
43-
"options": {
44-
"localProxyPort": 9997
45-
},
46-
"applications": {
47-
"web": {
48-
"development": {
49-
"local": { "port": 3000 }
50-
}
51-
}
52-
}
53-
}"#;
48+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
49+
let port = listener.local_addr().unwrap().port();
5450

55-
let config = Config::from_str(config_json, "test.json").unwrap();
56-
let server = ProxyServer::new(config).unwrap();
51+
let config_json = format!(
52+
r#"{{
53+
"options": {{
54+
"localProxyPort": {port}
55+
}},
56+
"applications": {{
57+
"web": {{
58+
"development": {{
59+
"local": {{ "port": 3000 }}
60+
}}
61+
}}
62+
}}
63+
}}"#
64+
);
5765

58-
let _listener = TcpListener::bind("127.0.0.1:9997").await.unwrap();
66+
let config = Config::from_str(&config_json, "test.json").unwrap();
67+
let server = ProxyServer::new(config).unwrap();
5968

6069
let result = server.check_port_available().await;
6170
assert!(!result, "Port should not be available when already bound");
71+
72+
drop(listener);
6273
}
6374

6475
#[tokio::test]
@@ -142,20 +153,26 @@ async fn test_multiple_child_apps() {
142153

143154
#[tokio::test]
144155
async fn test_proxy_server_creation() {
145-
let config_json = r#"{
146-
"options": {
147-
"localProxyPort": 4000
148-
},
149-
"applications": {
150-
"web": {
151-
"development": {
152-
"local": { "port": 3000 }
153-
}
154-
}
155-
}
156-
}"#;
156+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
157+
let port = listener.local_addr().unwrap().port();
158+
drop(listener);
157159

158-
let config = Config::from_str(config_json, "test.json").unwrap();
160+
let config_json = format!(
161+
r#"{{
162+
"options": {{
163+
"localProxyPort": {port}
164+
}},
165+
"applications": {{
166+
"web": {{
167+
"development": {{
168+
"local": {{ "port": 3000 }}
169+
}}
170+
}}
171+
}}
172+
}}"#
173+
);
174+
175+
let config = Config::from_str(&config_json, "test.json").unwrap();
159176
let server = ProxyServer::new(config);
160177

161178
assert!(server.is_ok());
@@ -197,33 +214,29 @@ async fn test_pattern_matching_edge_cases() {
197214
);
198215
}
199216

200-
async fn find_available_port_range(count: usize) -> Result<Vec<u16>, Box<dyn std::error::Error>> {
201-
// Try to find consecutive available ports within the allowed range (3000-9999)
217+
async fn find_available_port_range(
218+
count: usize,
219+
) -> Result<(Vec<u16>, Vec<TcpListener>), Box<dyn std::error::Error>> {
202220
let mut available_ports = Vec::new();
221+
let mut listeners = Vec::new();
203222

204223
for port in 3000..=9999 {
205-
// Skip commonly blocked ports
206224
if [3306, 5432, 6379].contains(&port) {
207225
continue;
208226
}
209-
if TcpListener::bind(format!("127.0.0.1:{port}")).await.is_ok() {
227+
if let Ok(listener) = TcpListener::bind(format!("127.0.0.1:{port}")).await {
210228
available_ports.push(port);
229+
listeners.push(listener);
211230
if available_ports.len() == count {
212-
return Ok(available_ports);
231+
return Ok((available_ports, listeners));
213232
}
214233
}
215234
}
216235
Err("Not enough available ports in allowed range".into())
217236
}
218237

219-
async fn mock_server(
220-
port: u16,
221-
response_text: &'static str,
222-
) -> Result<tokio::task::JoinHandle<()>, Box<dyn std::error::Error>> {
223-
let addr = SocketAddr::from(([127, 0, 0, 1], port));
224-
let listener = TcpListener::bind(addr).await?;
225-
226-
let handle = tokio::spawn(async move {
238+
fn mock_server(listener: TcpListener, response_text: &'static str) -> tokio::task::JoinHandle<()> {
239+
tokio::spawn(async move {
227240
loop {
228241
let (stream, _) = listener.accept().await.unwrap();
229242
let io = TokioIo::new(stream);
@@ -241,21 +254,27 @@ async fn mock_server(
241254
.serve_connection(io, service)
242255
.await;
243256
}
244-
});
245-
246-
tokio::time::sleep(WEBSOCKET_CLOSE_DELAY).await;
247-
Ok(handle)
257+
})
248258
}
249259

250-
#[tokio::test]
260+
#[tokio::test(flavor = "multi_thread")]
261+
#[serial]
251262
async fn test_end_to_end_proxy() {
252-
let ports = find_available_port_range(3).await.unwrap();
263+
let (ports, mut listeners) = find_available_port_range(3).await.unwrap();
253264
let web_port = ports[0];
254265
let docs_port = ports[1];
255266
let proxy_port = ports[2];
256267

257-
let web_handle = mock_server(web_port, "web app").await.unwrap();
258-
let docs_handle = mock_server(docs_port, "docs app").await.unwrap();
268+
let web_listener = listeners.remove(0);
269+
let docs_listener = listeners.remove(0);
270+
let proxy_listener = listeners.remove(0);
271+
272+
drop(proxy_listener);
273+
274+
let web_handle = mock_server(web_listener, "web app");
275+
let docs_handle = mock_server(docs_listener, "docs app");
276+
277+
tokio::time::sleep(Duration::from_millis(100)).await;
259278

260279
let config_json = format!(
261280
r#"{{
@@ -288,30 +307,36 @@ async fn test_end_to_end_proxy() {
288307
let shutdown_handle = server.shutdown_handle();
289308

290309
tokio::spawn(async move {
291-
server.run().await.unwrap();
310+
let _ = server.run().await;
292311
});
293312

294-
tokio::time::sleep(Duration::from_millis(200)).await;
313+
tokio::time::sleep(Duration::from_millis(300)).await;
295314

296315
let connector = hyper_util::client::legacy::connect::HttpConnector::new();
297316
let client: Client<_, Full<Bytes>> =
298317
Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector);
299318

300-
let web_response = client
301-
.get(format!("http://127.0.0.1:{proxy_port}/").parse().unwrap())
302-
.await
303-
.unwrap();
319+
let web_response = tokio::time::timeout(
320+
Duration::from_secs(5),
321+
client.get(format!("http://127.0.0.1:{proxy_port}/").parse().unwrap()),
322+
)
323+
.await
324+
.expect("Request timed out")
325+
.expect("Request failed");
304326
let web_body = web_response.into_body().collect().await.unwrap().to_bytes();
305327
assert_eq!(web_body, "web app");
306328

307-
let docs_response = client
308-
.get(
329+
let docs_response = tokio::time::timeout(
330+
Duration::from_secs(5),
331+
client.get(
309332
format!("http://127.0.0.1:{proxy_port}/docs")
310333
.parse()
311334
.unwrap(),
312-
)
313-
.await
314-
.unwrap();
335+
),
336+
)
337+
.await
338+
.expect("Request timed out")
339+
.expect("Request failed");
315340
let docs_body = docs_response
316341
.into_body()
317342
.collect()
@@ -320,14 +345,17 @@ async fn test_end_to_end_proxy() {
320345
.to_bytes();
321346
assert_eq!(docs_body, "docs app");
322347

323-
let docs_subpath_response = client
324-
.get(
348+
let docs_subpath_response = tokio::time::timeout(
349+
Duration::from_secs(5),
350+
client.get(
325351
format!("http://127.0.0.1:{proxy_port}/docs/api/reference")
326352
.parse()
327353
.unwrap(),
328-
)
329-
.await
330-
.unwrap();
354+
),
355+
)
356+
.await
357+
.expect("Request timed out")
358+
.expect("Request failed");
331359
let docs_subpath_body = docs_subpath_response
332360
.into_body()
333361
.collect()
@@ -337,10 +365,12 @@ async fn test_end_to_end_proxy() {
337365
assert_eq!(docs_subpath_body, "docs app");
338366

339367
let _ = shutdown_handle.send(());
340-
let _ = tokio::time::timeout(Duration::from_secs(2), shutdown_complete_rx).await;
368+
let _ = tokio::time::timeout(Duration::from_secs(3), shutdown_complete_rx).await;
341369

342370
web_handle.abort();
343371
docs_handle.abort();
372+
373+
tokio::time::sleep(Duration::from_millis(100)).await;
344374
}
345375

346376
#[tokio::test]

0 commit comments

Comments
 (0)