Skip to content

Commit 04b60aa

Browse files
committed
test: extract shared test helpers; scaffold psql-driven integration suite
Refactor: `start_gateway`, `test_config`, `trino_config` move from `tests/integration_test.rs` to a shared `tests/common/mod.rs`. The existing tokio-postgres-driven suite continues to work unchanged (28 passed + 3 ignored against a real Trino on SDP). New: `tests/psql_integration_test.rs` — 10 tests that drive the gateway via the real `psql` command-line client (libpq) instead of tokio-postgres. Catches client-specific issues that an embedded driver wouldn't surface — startup negotiation, notice handling, psql's simple-query-with-CommandComplete flow. Both suites are NOT replacements for each other: tokio-postgres gives breadth (200+ cases, fast), psql gives a real-binary signal (10 cases, ~50ms each via subprocess). The psql suite is currently `#[ignore]`'d on every test with note "psql subprocess hangs in test harness — debug in progress". Manual runs of the same psql command against the gateway-as-binary work fine; the hang only reproduces inside `Command::new("psql").output()`. Suspected hypotheses (in priority order): subprocess inherits something from the cargo-test parent that blocks (already added `Stdio::null()` for stdin, `--no-psqlrc` to avoid `\timing on` interference; hang persists), tokio runtime tear-down deadlock, in-process gateway accept-loop holding something open. Reproducer in TODO.md. Run manually with: kubectl port-forward svc/simple-trino-coordinator 18443:8443 & TRINO_HOST=127.0.0.1 TRINO_PORT=18443 TRINO_SSL=true \ TRINO_TLS_NO_VERIFY=true TRINO_CATALOG=tpch TRINO_SCHEMA=sf1 \ cargo test --test psql_integration_test -- --ignored \ --test-threads=1 \ --nocapture Also: add `*.pcap` and `*.pcapng` to `.gitignore`. tcpdump/Wireshark captures frequently contain customer data (hostnames, IPs, query content); keeping them out of the repo by default.
1 parent 9c20554 commit 04b60aa

4 files changed

Lines changed: 464 additions & 74 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@ TODO_TRIAGE.md
1717
# Operator-local Trino config and ad-hoc debug captures.
1818
trino.yaml
1919
/debuglogs/
20+
21+
# Wireshark / tcpdump captures — frequently contain customer data
22+
# (hostnames, IPs, query content). Keep out of the repo.
23+
*.pcap
24+
*.pcapng

tests/common/mod.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// SPDX-FileCopyrightText: 2026 Stackable GmbH
2+
// SPDX-License-Identifier: OSL-3.0
3+
4+
//! Shared test infrastructure used by both `integration_test.rs`
5+
//! (tokio-postgres-driven) and `psql_integration_test.rs` (real psql
6+
//! subprocess-driven).
7+
8+
#![allow(dead_code)] // not every helper is used by every test file
9+
10+
use std::net::SocketAddr;
11+
use std::sync::Arc;
12+
13+
use tokio::net::TcpListener;
14+
15+
use postgresql_trino_gateway::config::Config;
16+
use postgresql_trino_gateway::handler::GatewayHandlerFactory;
17+
use postgresql_trino_gateway::query_extended::GatewayExtendedQueryHandler;
18+
use postgresql_trino_gateway::query_simple::GatewayQueryHandler;
19+
use postgresql_trino_gateway::startup::GatewayStartupHandler;
20+
21+
/// Bind a fresh in-process gateway to `127.0.0.1:0` and return the
22+
/// resulting `SocketAddr`. The gateway runs in a background tokio task
23+
/// and accepts connections until the test binary exits.
24+
pub async fn start_gateway(config: Config) -> SocketAddr {
25+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
26+
let addr = listener.local_addr().unwrap();
27+
let config = Arc::new(config);
28+
let factory = Arc::new(GatewayHandlerFactory::new(
29+
Arc::new(GatewayStartupHandler {
30+
config: config.clone(),
31+
}),
32+
Arc::new(GatewayQueryHandler),
33+
Arc::new(GatewayExtendedQueryHandler),
34+
));
35+
tokio::spawn(async move {
36+
while let Ok((socket, _)) = listener.accept().await {
37+
let factory = factory.clone();
38+
tokio::spawn(async move {
39+
let _ = pgwire::tokio::process_socket(socket, None, factory).await;
40+
});
41+
}
42+
});
43+
addr
44+
}
45+
46+
/// Default Config for tests that don't reach Trino. Used by the
47+
/// gateway-only tests in `integration_test.rs` (intercepts, catalog
48+
/// stubs, server-function probes).
49+
pub fn test_config() -> Config {
50+
Config {
51+
listen_addr: "127.0.0.1:0".to_string(),
52+
tls_cert: None,
53+
tls_key: None,
54+
trino_host: "localhost".to_string(),
55+
trino_port: 8080,
56+
trino_catalog: "memory".to_string(),
57+
trino_schema: "default".to_string(),
58+
trino_user: "trino".to_string(),
59+
trino_ssl: false,
60+
trino_tls_no_verify: false,
61+
trino_allow_plaintext_auth: false,
62+
auth: false,
63+
allow_insecure_listener: false,
64+
max_connections: 256,
65+
}
66+
}
67+
68+
/// Build a Config pointing at a real Trino, sourced from the
69+
/// `TRINO_HOST` / `TRINO_PORT` / `TRINO_SSL` / `TRINO_TLS_NO_VERIFY` /
70+
/// `TRINO_CATALOG` / `TRINO_SCHEMA` env vars. Returns `None` when
71+
/// `TRINO_HOST` is unset, so test functions can early-return with a
72+
/// "skipping" message.
73+
pub fn trino_config() -> Option<Config> {
74+
let host = std::env::var("TRINO_HOST").ok()?;
75+
let port: u16 = std::env::var("TRINO_PORT").ok()?.parse().ok()?;
76+
let ssl = std::env::var("TRINO_SSL").ok().is_some_and(|v| v == "true");
77+
let tls_no_verify = std::env::var("TRINO_TLS_NO_VERIFY")
78+
.ok()
79+
.is_some_and(|v| v == "true");
80+
let catalog = std::env::var("TRINO_CATALOG").unwrap_or_else(|_| "tpch".to_string());
81+
let schema = std::env::var("TRINO_SCHEMA").unwrap_or_else(|_| "sf1".to_string());
82+
Some(Config {
83+
listen_addr: "127.0.0.1:0".to_string(),
84+
tls_cert: None,
85+
tls_key: None,
86+
trino_host: host,
87+
trino_port: port,
88+
trino_catalog: catalog,
89+
trino_schema: schema,
90+
trino_user: "trino".to_string(),
91+
trino_ssl: ssl,
92+
trino_tls_no_verify: tls_no_verify,
93+
trino_allow_plaintext_auth: false,
94+
auth: false,
95+
allow_insecure_listener: false,
96+
max_connections: 256,
97+
})
98+
}

tests/integration_test.rs

Lines changed: 3 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,18 @@
77
#![allow(clippy::panic, clippy::unwrap_used)]
88

99
use std::net::SocketAddr;
10-
use std::sync::Arc;
1110

12-
use tokio::net::TcpListener;
1311
use tokio_postgres::{Client, NoTls, SimpleQueryMessage};
1412

1513
use postgresql_trino_gateway::config::Config;
16-
use postgresql_trino_gateway::handler::GatewayHandlerFactory;
17-
use postgresql_trino_gateway::query_extended::GatewayExtendedQueryHandler;
18-
use postgresql_trino_gateway::query_simple::GatewayQueryHandler;
19-
use postgresql_trino_gateway::startup::GatewayStartupHandler;
14+
15+
mod common;
16+
use common::{start_gateway, test_config, trino_config};
2017

2118
// ---------------------------------------------------------------------------
2219
// Test infrastructure
2320
// ---------------------------------------------------------------------------
2421

25-
async fn start_gateway(config: Config) -> SocketAddr {
26-
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
27-
let addr = listener.local_addr().unwrap();
28-
let config = Arc::new(config);
29-
let factory = Arc::new(GatewayHandlerFactory::new(
30-
Arc::new(GatewayStartupHandler {
31-
config: config.clone(),
32-
}),
33-
Arc::new(GatewayQueryHandler),
34-
Arc::new(GatewayExtendedQueryHandler),
35-
));
36-
tokio::spawn(async move {
37-
while let Ok((socket, _)) = listener.accept().await {
38-
let factory = factory.clone();
39-
tokio::spawn(async move {
40-
let _ = pgwire::tokio::process_socket(socket, None, factory).await;
41-
});
42-
}
43-
});
44-
addr
45-
}
46-
4722
async fn connect(addr: SocketAddr) -> Client {
4823
connect_as(addr, "trino", None).await
4924
}
@@ -77,52 +52,6 @@ fn extract_rows(messages: Vec<SimpleQueryMessage>) -> Vec<tokio_postgres::Simple
7752
.collect()
7853
}
7954

80-
fn test_config() -> Config {
81-
Config {
82-
listen_addr: "127.0.0.1:0".to_string(),
83-
tls_cert: None,
84-
tls_key: None,
85-
trino_host: "localhost".to_string(),
86-
trino_port: 8080,
87-
trino_catalog: "memory".to_string(),
88-
trino_schema: "default".to_string(),
89-
trino_user: "trino".to_string(),
90-
trino_ssl: false,
91-
trino_tls_no_verify: false,
92-
trino_allow_plaintext_auth: false,
93-
auth: false,
94-
allow_insecure_listener: false,
95-
max_connections: 256,
96-
}
97-
}
98-
99-
fn trino_config() -> Option<Config> {
100-
let host = std::env::var("TRINO_HOST").ok()?;
101-
let port: u16 = std::env::var("TRINO_PORT").ok()?.parse().ok()?;
102-
let ssl = std::env::var("TRINO_SSL").ok().is_some_and(|v| v == "true");
103-
let tls_no_verify = std::env::var("TRINO_TLS_NO_VERIFY")
104-
.ok()
105-
.is_some_and(|v| v == "true");
106-
let catalog = std::env::var("TRINO_CATALOG").unwrap_or_else(|_| "tpch".to_string());
107-
let schema = std::env::var("TRINO_SCHEMA").unwrap_or_else(|_| "sf1".to_string());
108-
Some(Config {
109-
listen_addr: "127.0.0.1:0".to_string(),
110-
tls_cert: None,
111-
tls_key: None,
112-
trino_host: host,
113-
trino_port: port,
114-
trino_catalog: catalog,
115-
trino_schema: schema,
116-
trino_user: "trino".to_string(),
117-
trino_ssl: ssl,
118-
trino_tls_no_verify: tls_no_verify,
119-
trino_allow_plaintext_auth: false,
120-
auth: false,
121-
allow_insecure_listener: false,
122-
max_connections: 256,
123-
})
124-
}
125-
12655
/// A test case: SQL to execute and how to check the result.
12756
enum Check {
12857
/// Query returns rows. `min_rows` is the minimum expected count.

0 commit comments

Comments
 (0)