Skip to content

Commit 43a9760

Browse files
committed
WIP refactor: new #[sqlx::test] architecture
1 parent 1d674f5 commit 43a9760

5 files changed

Lines changed: 135 additions & 7 deletions

File tree

sqlx-core/Cargo.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ json = ["serde", "serde_json"]
2121

2222
# for conditional compilation
2323
_rt-async-global-executor = ["async-global-executor", "_rt-async-io", "_rt-async-task"]
24-
_rt-async-io = ["async-io", "async-fs"] # see note at async-fs declaration
24+
_rt-async-io = ["async-io", "async-fs", "async-lock"] # see note at async-fs declaration
2525
_rt-async-std = ["async-std", "_rt-async-io"]
2626
_rt-async-task = ["async-task"]
2727
_rt-smol = ["smol", "_rt-async-io", "_rt-async-task"]
28-
_rt-tokio = ["tokio", "tokio-stream"]
28+
_rt-tokio = ["tokio", "tokio-stream", "tokio/rt"] # `rt` feature is almost always going to be enabled anyway
2929

3030
_tls-native-tls = ["native-tls"]
3131
_tls-rustls-aws-lc-rs = ["_tls-rustls", "rustls/aws-lc-rs", "webpki-roots"]
@@ -72,6 +72,7 @@ uuid = { workspace = true, optional = true }
7272

7373
# work around bug in async-fs 2.0.0, which references futures-lite dependency wrongly, see https://github.com/launchbadge/sqlx/pull/3791#issuecomment-3043363281
7474
async-fs = { version = "2.1", optional = true }
75+
async-lock = { version = "3.4.2", optional = true }
7576
async-io = { version = "2.4.1", optional = true }
7677
async-task = { version = "4.7.1", optional = true }
7778

@@ -83,7 +84,6 @@ crossbeam-queue = "0.3.2"
8384
either = "1.6.1"
8485
futures-core = { version = "0.3.32", default-features = false }
8586
futures-io = "0.3.32"
86-
futures-intrusive = "0.5.0"
8787
futures-util = { version = "0.3.32", default-features = false, features = ["alloc", "sink", "io"] }
8888
log = { version = "0.4.18", default-features = false }
8989
memchr = { version = "2.5.0", default-features = false }
@@ -103,10 +103,9 @@ indexmap = "2.0"
103103
event-listener = "5.2.0"
104104
hashbrown = "0.16.0"
105105

106-
thiserror.workspace = true
106+
futures-intrusive = "0.5.0"
107107

108-
[dev-dependencies]
109-
tokio = { version = "1.25.0", features = ["rt"] }
108+
thiserror.workspace = true
110109

111110
[dev-dependencies.sqlx]
112111
# FIXME: https://github.com/rust-lang/cargo/issues/15622

sqlx-core/src/sync.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use cfg_if::cfg_if;
2+
use std::future::Future;
23

34
// For types with identical signatures that don't require runtime support,
45
// we can just arbitrarily pick one to use based on what's enabled.
@@ -204,3 +205,60 @@ impl AsyncSemaphoreReleaser<'_> {
204205
}
205206
}
206207
}
208+
209+
pub struct AsyncOnceCell<T> {
210+
#[cfg(feature = "_rt-tokio")]
211+
inner: tokio::sync::OnceCell<T>,
212+
213+
#[cfg(all(feature = "_rt-async-io", not(feature = "_rt-tokio")))]
214+
inner: async_lock::OnceCell<T>,
215+
216+
#[cfg(not(any(feature = "_rt-async-std", feature = "_rt-tokio")))]
217+
phantom: std::marker::PhantomData<T>,
218+
}
219+
220+
impl<T> AsyncOnceCell<T> {
221+
pub fn new() -> Self {
222+
cfg_if! {
223+
if #[cfg(feature = "_rt-tokio")] {
224+
Self { inner: tokio::sync::OnceCell::new() }
225+
} else if #[cfg(feature = "_rt-async-io")] {
226+
Self { inner: async_lock::OnceCell::new() }
227+
} else {
228+
crate::rt::missing_rt(());
229+
}
230+
}
231+
}
232+
233+
pub const fn const_new() -> Self {
234+
cfg_if! {
235+
if #[cfg(feature = "_rt-tokio")] {
236+
Self { inner: tokio::sync::OnceCell::const_new() }
237+
} else if #[cfg(feature = "_rt-async-io")] {
238+
Self { inner: async_lock::OnceCell::new() }
239+
} else {
240+
crate::rt::missing_rt(());
241+
}
242+
}
243+
}
244+
245+
pub async fn get_or_try_init<F, Fut, E>(&self, f: F) -> Result<&T, E>
246+
where
247+
F: FnOnce() -> Fut,
248+
Fut: Future<Output = Result<T, E>>,
249+
{
250+
cfg_if! {
251+
if #[cfg(any(feature = "_rt-tokio", feature = "_rt-async-io"))] {
252+
self.inner.get_or_try_init(f).await
253+
} else {
254+
crate::rt::missing_rt(f)
255+
}
256+
}
257+
}
258+
}
259+
260+
impl<T> Default for AsyncOnceCell<T> {
261+
fn default() -> Self {
262+
Self::new()
263+
}
264+
}

sqlx-core/src/testing/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::migrate::{Migrate, Migrator};
1313
use crate::pool::{Pool, PoolConnection, PoolOptions};
1414

1515
mod fixtures;
16+
mod pool;
1617

1718
pub trait TestSupport: Database {
1819
/// Get parameters to construct a `Pool` suitable for testing.
@@ -66,6 +67,7 @@ pub struct TestArgs {
6667
pub test_path: &'static str,
6768
pub migrator: Option<&'static Migrator>,
6869
pub fixtures: &'static [TestFixture],
70+
pub max_connections: usize,
6971
}
7072

7173
pub trait TestFn {
@@ -158,6 +160,7 @@ impl TestArgs {
158160
test_path,
159161
migrator: None,
160162
fixtures: &[],
163+
max_connections: 5,
161164
}
162165
}
163166

@@ -168,6 +171,10 @@ impl TestArgs {
168171
pub fn fixtures(&mut self, fixtures: &'static [TestFixture]) {
169172
self.fixtures = fixtures;
170173
}
174+
175+
pub fn max_connections(&mut self, max_connections: usize) {
176+
self.max_connections = max_connections;
177+
}
171178
}
172179

173180
impl TestTermination for () {

sqlx-core/src/testing/pool.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use std::rc::Weak;
2+
use crate::database::Database;
3+
use crate::pool::Pool;
4+
use crate::sync::AsyncOnceCell;
5+
6+
pub struct TestMasterPool<DB: Database> {
7+
inner: AsyncOnceCell<Inner<DB>>,
8+
}
9+
10+
struct Inner<DB: Database> {
11+
pool: Pool<DB>,
12+
13+
#[cfg(feature = "_rt-tokio")]
14+
15+
}
16+
17+
18+
impl<DB: Database> TestMasterPool<DB> {
19+
20+
}

sqlx-postgres/src/testing/mod.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,51 @@ async fn test_context(args: &TestArgs) -> Result<TestContext<Postgres>, Error> {
146146
create index if not exists databases_created_at
147147
on _sqlx_test.databases(created_at);
148148
149-
create sequence if not exists _sqlx_test.database_ids;
149+
create table if not exists _sqlx_test.tests (
150+
test_id int8 primary key generated always as identity,
151+
-- Automatically cleans up leaked test runs as well
152+
db_name text not null references _sqlx_test.databases(db_name) on delete cascade,
153+
required_connections int4 not null
154+
check (required_connections > 0 and required_connections <= max_connections),
155+
-- Each test's `SQLX_TEST_MAX_CONNECTIONS`, ideally all the same
156+
max_connections int4 not null check (max_connections > 0),
157+
started_at timestamptz not null default now()
158+
);
159+
160+
create or replace function _sqlx_test.tests_check_max_connections()
161+
returns trigger as
162+
$$
163+
declare
164+
used_connections int4;
165+
max_required_connections int4;
166+
max_connections int4;
167+
begin
168+
select
169+
sum(required_connections),
170+
max(required_connections),
171+
-- Abide by the highest `SQLX_TEST_MAX_CONNECTIONS`
172+
max(max_connections)
173+
into
174+
used_connections,
175+
max_required_connections,
176+
max_connections
177+
from _sqlx_test.tests;
178+
179+
if max_required_connections > max_connections then
180+
raise
181+
'max(required_connections) exceeds min(max_connections) of any process'
182+
using constraint = 'required_connections_exceeds_max';
183+
elsif max_connections > max_connections then
184+
raise 'not enough spare connections available; used: %i, total: %i',
185+
used_connections, max_connections
186+
using constraint = 'insufficient_connections_available';
187+
end if;
188+
end;
189+
$$
190+
language plpgsql;
191+
192+
create or replace constraint trigger check_max_connections after insert on _sqlx_test.tests
193+
for each statement execute function _sqlx_test.check_max_connections();
150194
"#,
151195
)
152196
.await?;

0 commit comments

Comments
 (0)