Skip to content

Commit 5aa61a5

Browse files
committed
Include integration test. Setup CI to run all tests
1 parent 79dc72b commit 5aa61a5

5 files changed

Lines changed: 312 additions & 6 deletions

File tree

.github/workflows/test.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Test Suite
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
jobs:
9+
integration-test:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Install Rust
18+
uses: dtolnay/rust-toolchain@stable
19+
20+
- name: Cache Rust artifacts
21+
uses: Swatinem/rust-cache@v2
22+
23+
- name: Run unit tests
24+
run: cargo test --lib
25+
26+
- name: Run integration snapshot test
27+
run: cargo test --test integration_test

Cargo.lock

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

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ async-trait = "0.1"
2424
regex = "1.0"
2525
signal-hook = "0.3"
2626

27+
[dev-dependencies]
28+
insta = { version = "1", features = ["yaml"] }
29+
2730
[profile.release]
2831
lto = "thin"
2932
codegen-units = 1

src/cli/runner.rs

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use datafusion::arrow::record_batch::RecordBatch;
22
use datafusion::common::instant::Instant;
33
use rand::rngs::StdRng;
44
use rand::{Rng, SeedableRng};
5+
use std::fs::OpenOptions;
6+
use std::io::Write;
57
use std::sync::Arc;
68
use std::time::Duration;
79
use tracing::{error, info, warn};
@@ -49,7 +51,7 @@ pub async fn run_fuzzer(ctx: Arc<GlobalContext>) -> Result<()> {
4951
let query_seed = query_base_seed.wrapping_add(i as u64);
5052

5153
// >>> CORE LOGIC <<<
52-
execute_oracle_test(query_seed, &ctx).await;
54+
let _ = execute_oracle_test(round, i, query_seed, &ctx).await?;
5355
}
5456

5557
update_stat_for_round_completion(&ctx.fuzzer_stats);
@@ -206,7 +208,12 @@ async fn create_and_register_view(
206208
Ok(())
207209
}
208210

209-
async fn execute_oracle_test(seed: u64, ctx: &Arc<GlobalContext>) -> bool {
211+
async fn execute_oracle_test(
212+
round: u32,
213+
query_index: u32,
214+
seed: u64,
215+
ctx: &Arc<GlobalContext>,
216+
) -> Result<bool> {
210217
// Create a deterministic RNG instance for this test
211218
let mut rng = StdRng::seed_from_u64(seed);
212219

@@ -229,15 +236,24 @@ async fn execute_oracle_test(seed: u64, ctx: &Arc<GlobalContext>) -> bool {
229236
if !is_error_whitelisted(&err_msg, None) {
230237
error!(err_msg)
231238
}
232-
return false;
239+
return Ok(false);
233240
}
234241
};
235242

236243
if query_group.is_empty() {
237244
warn!("Oracle generated empty query group");
238-
return false;
245+
return Ok(false);
239246
}
240247

248+
append_query_log(
249+
ctx,
250+
round,
251+
query_index,
252+
seed,
253+
selected_oracle.name(),
254+
&query_group,
255+
)?;
256+
241257
// === Execute queries and collect results ===
242258
let mut execution_results = Vec::new();
243259
for query_context in query_group {
@@ -259,7 +275,7 @@ async fn execute_oracle_test(seed: u64, ctx: &Arc<GlobalContext>) -> bool {
259275
{
260276
Ok(_) => {
261277
info!("Oracle test passed");
262-
true
278+
Ok(true)
263279
}
264280
Err(e) => {
265281
error!("Oracle test failed: {}", e);
@@ -268,9 +284,61 @@ async fn execute_oracle_test(seed: u64, ctx: &Arc<GlobalContext>) -> bool {
268284
if let Ok(error_report) = selected_oracle.create_error_report(&execution_results) {
269285
error!("Error Report:\n{}", error_report);
270286
}
271-
false
287+
Ok(false)
288+
}
289+
}
290+
}
291+
292+
fn append_query_log(
293+
ctx: &Arc<GlobalContext>,
294+
round: u32,
295+
query_index: u32,
296+
query_seed: u64,
297+
oracle_name: &str,
298+
query_group: &[QueryContext],
299+
) -> Result<()> {
300+
let Some(log_dir) = &ctx.runner_config.log_path else {
301+
return Ok(());
302+
};
303+
304+
let query_log_path = log_dir.join("queries.log");
305+
let mut file = OpenOptions::new()
306+
.create(true)
307+
.append(true)
308+
.open(&query_log_path)
309+
.map_err(|e| {
310+
crate::common::fuzzer_err(&format!(
311+
"Failed to open query log '{}': {}",
312+
query_log_path.display(),
313+
e
314+
))
315+
})?;
316+
317+
writeln!(
318+
file,
319+
"=== round={} query={} oracle={} query_seed={} ===",
320+
round + 1,
321+
query_index + 1,
322+
oracle_name,
323+
query_seed
324+
)?;
325+
326+
for (statement_index, query_context) in query_group.iter().enumerate() {
327+
match &query_context.context_description {
328+
Some(description) => writeln!(
329+
file,
330+
"--- statement={} context={} ---",
331+
statement_index + 1,
332+
description
333+
)?,
334+
None => writeln!(file, "--- statement={} ---", statement_index + 1)?,
272335
}
336+
337+
writeln!(file, "{}", query_context.query)?;
338+
writeln!(file)?;
273339
}
340+
341+
Ok(())
274342
}
275343

276344
/// Query execution result that tracks both the outcome and whether it timed out

0 commit comments

Comments
 (0)