Skip to content

Commit 1166c09

Browse files
committed
add tests in procedure-client
1 parent 55de2b2 commit 1166c09

11 files changed

Lines changed: 387 additions & 27 deletions

File tree

crates/bindings/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,8 @@ impl ProcedureContext {
11861186

11871187
let run = || {
11881188
// Start the transaction.
1189+
1190+
use core::mem;
11891191
let timestamp = sys::procedure::procedure_start_mut_tx().expect(
11901192
"holding `&mut ProcedureContext`, so should not be in a tx already; called manually elsewhere?",
11911193
);
@@ -1207,7 +1209,7 @@ impl ProcedureContext {
12071209
let abort_guard = DoOnDrop(abort);
12081210
let res = body(&tx);
12091211
// Defuse the bomb.
1210-
let DoOnDrop(_) = abort_guard;
1212+
mem::forget(abort_guard);
12111213
res
12121214
};
12131215

modules/sdk-test-procedure/src/lib.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use spacetimedb::{procedure, ProcedureContext, SpacetimeType};
1+
use spacetimedb::{procedure, table, ProcedureContext, SpacetimeType, Table, TxContext};
22

33
#[derive(SpacetimeType)]
44
struct ReturnStruct {
@@ -45,8 +45,42 @@ fn will_panic(_ctx: &mut ProcedureContext) {
4545
// and returns some value derived from the error.
4646
// Then write a test which invokes it in the Rust client SDK test suite.
4747

48-
// TODO(procedure-tx): Add a procedure here which acquires a transaction, inserts a row, commits, then returns.
49-
// Then write a test which invokes it and asserts observing the row in the Rust client SDK test suite.
48+
#[table(public, name = my_table)]
49+
struct MyTable {
50+
field: ReturnStruct,
51+
}
52+
53+
fn insert_my_table(ctx: &TxContext) {
54+
ctx.db.my_table().insert(MyTable {
55+
field: ReturnStruct {
56+
a: 42,
57+
b: "magic".into(),
58+
},
59+
});
60+
}
61+
62+
fn assert_row_count(ctx: &mut ProcedureContext, count: u64) {
63+
ctx.with_tx(|ctx| {
64+
assert_eq!(count, ctx.db.my_table().count());
65+
});
66+
}
67+
68+
#[procedure]
69+
fn insert_with_tx_commit(ctx: &mut ProcedureContext) {
70+
// Insert a row and commit.
71+
ctx.with_tx(insert_my_table);
72+
73+
// Assert that there's a row.
74+
assert_row_count(ctx, 1);
75+
}
5076

51-
// TODO(procedure-tx): Add a procedure here which acquires a transaction, inserts a row, rolls back, then returns.
52-
// Then write a test which invokes it and asserts not observing the row in the Rust client SDK test suite.
77+
#[procedure]
78+
fn insert_with_tx_rollback(ctx: &mut ProcedureContext) {
79+
let _: Result<(), u32> = ctx.try_with_tx(|ctx| {
80+
insert_my_table(ctx);
81+
Err(24)
82+
});
83+
84+
// Assert that there's not a row.
85+
assert_row_count(ctx, 0);
86+
}
Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
1-
This test client is used with two modules:
1+
This test client is used with the module:
22

3-
- [`sdk-test-connect-disconnect`](/modules/sdk-test-connect-disconnect)
4-
- [`sdk-test-connect-disconnect-cs`](/modules/sdk-test-connect-disconnect-cs)
3+
- [`sdk-test-procedure`](/modules/sdk-test-procedure)
54

6-
Currently, the bindings are generated using only one of those two modules,
7-
chosen arbitrarily on each test run.
8-
The two tests which use this client,
9-
`connect_disconnect_callbacks` and `connect_disconnect_callbacks_csharp`,
10-
are not intended to test code generation.
11-
12-
The goal of the two tests is to verify that module-side `connect` and `disconnect` events
13-
fire when an SDK connects or disconnects via WebSocket,
14-
and that the client can observe mutations performed by those events.
5+
The goal of the test is to exercise various procedure related
6+
aspects of the (Rust) module ABI and the rust SDK.
157

168
To (re-)generate the `module_bindings`, from this directory, run:
179

1810
```sh
1911
mkdir -p src/module_bindings
2012
spacetime generate --lang rust \
2113
--out-dir src/module_bindings \
22-
--project-path ../../../../modules/sdk-test-connect-disconnect
14+
--project-path ../../../../modules/sdk-test-procedure
2315
```

sdks/rust/tests/procedure-client/src/main.rs

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
mod module_bindings;
22

3-
use module_bindings::*;
4-
53
use anyhow::Context;
6-
use spacetimedb_sdk::DbConnectionBuilder;
4+
use module_bindings::*;
5+
use spacetimedb_sdk::{DbConnectionBuilder, DbContext, Table};
76
use test_counter::TestCounter;
87

98
const LOCALHOST: &str = "http://localhost:3000";
@@ -39,10 +38,33 @@ fn main() {
3938
match &*test {
4039
"procedure-return-values" => exec_procedure_return_values(),
4140
"procedure-observe-panic" => exec_procedure_panic(),
41+
"insert-with-tx-commit" => exec_insert_with_tx_commit(),
42+
"insert-with-tx-rollback" => exec_insert_with_tx_rollback(),
4243
_ => panic!("Unknown test: {test}"),
4344
}
4445
}
4546

47+
fn assert_table_empty<T: Table>(tbl: T) -> anyhow::Result<()> {
48+
let count = tbl.count();
49+
if count != 0 {
50+
anyhow::bail!(
51+
"Expected table {} to be empty, but found {} rows resident",
52+
std::any::type_name::<T::Row>(),
53+
count,
54+
)
55+
}
56+
Ok(())
57+
}
58+
59+
/// Each subscribing test runs against a fresh DB,
60+
/// so all tables should be empty until we call an insert reducer.
61+
///
62+
/// We'll call this function within our initial `on_subscription_applied` callback to verify that.
63+
fn assert_all_tables_empty(ctx: &impl RemoteDbContext) -> anyhow::Result<()> {
64+
assert_table_empty(ctx.db().my_table())?;
65+
Ok(())
66+
}
67+
4668
fn connect_with_then(
4769
test_counter: &std::sync::Arc<TestCounter>,
4870
on_connect_suffix: &str,
@@ -71,6 +93,24 @@ fn connect_then(
7193
connect_with_then(test_counter, "", |x| x, callback)
7294
}
7395

96+
/// A query that subscribes to all rows from all tables.
97+
const SUBSCRIBE_ALL: &[&str] = &["SELECT * FROM my_table;"];
98+
99+
fn subscribe_all_then(ctx: &impl RemoteDbContext, callback: impl FnOnce(&SubscriptionEventContext) + Send + 'static) {
100+
subscribe_these_then(ctx, SUBSCRIBE_ALL, callback)
101+
}
102+
103+
fn subscribe_these_then(
104+
ctx: &impl RemoteDbContext,
105+
queries: &[&str],
106+
callback: impl FnOnce(&SubscriptionEventContext) + Send + 'static,
107+
) {
108+
ctx.subscription_builder()
109+
.on_applied(callback)
110+
.on_error(|_ctx, error| panic!("Subscription errored: {error:?}"))
111+
.subscribe(queries);
112+
}
113+
74114
fn exec_procedure_return_values() {
75115
let test_counter = TestCounter::new();
76116

@@ -142,3 +182,65 @@ fn exec_procedure_panic() {
142182

143183
test_counter.wait_for_all();
144184
}
185+
186+
fn exec_insert_with_tx_commit() {
187+
fn expected() -> ReturnStruct {
188+
ReturnStruct {
189+
a: 42,
190+
b: "magic".into(),
191+
}
192+
}
193+
194+
let test_counter = TestCounter::new();
195+
let sub_applied_nothing_result = test_counter.add_test("on_subscription_applied_nothing");
196+
let inspect_result = test_counter.add_test("insert_with_tx_commit_values");
197+
let mut callback_result = Some(test_counter.add_test("insert_with_tx_commit_callback"));
198+
199+
connect_then(&test_counter, {
200+
move |ctx| {
201+
ctx.db().my_table().on_insert(move |_, row| {
202+
assert_eq!(row.field, expected());
203+
(callback_result.take().unwrap())(Ok(()));
204+
});
205+
206+
subscribe_all_then(ctx, move |ctx| {
207+
sub_applied_nothing_result(assert_all_tables_empty(ctx));
208+
209+
ctx.procedures.insert_with_tx_commit_then(move |ctx, res| {
210+
assert!(res.is_ok());
211+
let row = ctx.db().my_table().iter().next().unwrap();
212+
assert_eq!(row.field, expected());
213+
inspect_result(Ok(()));
214+
});
215+
});
216+
}
217+
});
218+
219+
test_counter.wait_for_all();
220+
}
221+
222+
fn exec_insert_with_tx_rollback() {
223+
let test_counter = TestCounter::new();
224+
let sub_applied_nothing_result = test_counter.add_test("on_subscription_applied_nothing");
225+
let inspect_result = test_counter.add_test("insert_with_tx_rollback_values");
226+
227+
connect_then(&test_counter, {
228+
move |ctx| {
229+
ctx.db()
230+
.my_table()
231+
.on_insert(|_, _| unreachable!("should not have inserted a row"));
232+
233+
subscribe_all_then(ctx, move |ctx| {
234+
sub_applied_nothing_result(assert_all_tables_empty(ctx));
235+
236+
ctx.procedures.insert_with_tx_rollback_then(move |ctx, res| {
237+
assert!(res.is_ok());
238+
assert_eq!(ctx.db().my_table().iter().next(), None);
239+
inspect_result(Ok(()));
240+
});
241+
});
242+
}
243+
});
244+
245+
test_counter.wait_for_all();
246+
}

sdks/rust/tests/procedure-client/src/module_bindings/insert_with_tx_commit_procedure.rs

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

sdks/rust/tests/procedure-client/src/module_bindings/insert_with_tx_rollback_procedure.rs

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

0 commit comments

Comments
 (0)