Skip to content
This repository was archived by the owner on Sep 9, 2025. It is now read-only.

Commit 4834331

Browse files
author
Hendrik van Antwerpen
authored
Merge pull request #283 from github/test-timeout
Add flag to set max test runtime and limit CI test runner by default
2 parents 868614b + 6fc7dcc commit 4834331

File tree

2 files changed

+48
-36
lines changed

2 files changed

+48
-36
lines changed

tree-sitter-stack-graphs/src/ci.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
//! .run()
2525
//! }
2626
//! ```
27+
//!
28+
//! By default tests time out after 60 seconds. Set `Tester::max_test_time` to change the timeout.
2729
2830
use std::path::PathBuf;
31+
use std::time::Duration;
2932

3033
use crate::cli::test::TestArgs;
3134
use crate::loader::{LanguageConfiguration, Loader};
@@ -35,13 +38,15 @@ use crate::loader::{LanguageConfiguration, Loader};
3538
pub struct Tester {
3639
configurations: Vec<LanguageConfiguration>,
3740
test_paths: Vec<PathBuf>,
41+
pub max_test_time: Option<Duration>,
3842
}
3943

4044
impl Tester {
4145
pub fn new(configurations: Vec<LanguageConfiguration>, test_paths: Vec<PathBuf>) -> Self {
4246
Self {
4347
configurations,
4448
test_paths,
49+
max_test_time: Some(Duration::from_secs(60)),
4550
}
4651
}
4752

@@ -63,6 +68,8 @@ impl Tester {
6368
}
6469
let loader = Loader::from_language_configurations(self.configurations, None)
6570
.expect("Expected loader");
66-
TestArgs::new(test_paths).run(loader)
71+
let mut args = TestArgs::new(test_paths);
72+
args.max_test_time = self.max_test_time;
73+
args.run(loader)
6774
}
6875
}

tree-sitter-stack-graphs/src/cli/test.rs

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,23 @@ use stack_graphs::stitching::Database;
1919
use stack_graphs::stitching::ForwardPartialPathStitcher;
2020
use std::path::Path;
2121
use std::path::PathBuf;
22+
use std::time::Duration;
2223
use tree_sitter_graph::Variables;
2324

25+
use crate::cli::util::duration_from_seconds_str;
26+
use crate::cli::util::iter_files_and_directories;
27+
use crate::cli::util::ConsoleFileLogger;
2428
use crate::cli::util::ExistingPathBufValueParser;
29+
use crate::cli::util::FileLogger;
2530
use crate::cli::util::PathSpec;
2631
use crate::loader::ContentProvider;
2732
use crate::loader::FileReader;
2833
use crate::loader::LanguageConfiguration;
2934
use crate::loader::Loader;
3035
use crate::test::Test;
3136
use crate::test::TestResult;
37+
use crate::CancelAfterDuration;
3238
use crate::CancellationFlag;
33-
use crate::NoCancellation;
34-
35-
use super::util::iter_files_and_directories;
36-
use super::util::BuildErrorWithSource;
37-
use super::util::ConsoleFileLogger;
38-
use super::util::FileLogger;
3939

4040
#[derive(Args)]
4141
#[clap(after_help = r#"PATH SPECIFICATIONS:
@@ -128,6 +128,14 @@ pub struct TestArgs {
128128
/// Do not load builtins for tests.
129129
#[clap(long)]
130130
pub no_builtins: bool,
131+
132+
/// Maximum runtime per test in seconds.
133+
#[clap(
134+
long,
135+
value_name = "SECONDS",
136+
value_parser = duration_from_seconds_str,
137+
)]
138+
pub max_test_time: Option<Duration>,
131139
}
132140

133141
/// Flag to control output
@@ -158,6 +166,7 @@ impl TestArgs {
158166
save_visualization: None,
159167
output_mode: OutputMode::OnFailure,
160168
no_builtins: false,
169+
max_test_time: None,
161170
}
162171
}
163172

@@ -198,7 +207,7 @@ impl TestArgs {
198207
loader: &mut Loader,
199208
file_status: &mut ConsoleFileLogger,
200209
) -> anyhow::Result<TestResult> {
201-
let cancellation_flag = &NoCancellation;
210+
let cancellation_flag = CancelAfterDuration::from_option(self.max_test_time);
202211

203212
// If the file is skipped (ending in .skip) we construct the non-skipped path to see if we would support it.
204213
let load_path = if test_path.extension().map_or(false, |e| e == "skip") {
@@ -208,7 +217,7 @@ impl TestArgs {
208217
};
209218
let mut file_reader = MappingFileReader::new(&load_path, test_path);
210219
let lc = match loader
211-
.load_for_file(&load_path, &mut file_reader, cancellation_flag)?
220+
.load_for_file(&load_path, &mut file_reader, cancellation_flag.as_ref())?
212221
.primary
213222
{
214223
Some(lc) => lc,
@@ -250,36 +259,21 @@ impl TestArgs {
250259
&test_fragment.source,
251260
&mut all_paths,
252261
&test_fragment.globals,
253-
cancellation_flag,
262+
cancellation_flag.as_ref(),
254263
)
255-
.map_err(|inner| BuildErrorWithSource {
256-
inner,
257-
source_path: test_path.to_path_buf(),
258-
source_str: &test_fragment.source,
259-
tsg_path: PathBuf::new(),
260-
tsg_str: "",
261-
})
262264
} else if lc.matches_file(
263265
&test_fragment.path,
264266
&mut Some(test_fragment.source.as_ref()),
265267
)? {
266268
globals.clear();
267269
test_fragment.add_globals_to(&mut globals);
268-
lc.sgl
269-
.build_stack_graph_into(
270-
&mut test.graph,
271-
test_fragment.file,
272-
&test_fragment.source,
273-
&globals,
274-
cancellation_flag,
275-
)
276-
.map_err(|inner| BuildErrorWithSource {
277-
inner,
278-
source_path: test_path.to_path_buf(),
279-
source_str: &test_fragment.source,
280-
tsg_path: lc.sgl.tsg_path().to_path_buf(),
281-
tsg_str: &lc.sgl.tsg_source(),
282-
})
270+
lc.sgl.build_stack_graph_into(
271+
&mut test.graph,
272+
test_fragment.file,
273+
&test_fragment.source,
274+
&globals,
275+
cancellation_flag.as_ref(),
276+
)
283277
} else {
284278
return Err(anyhow!(
285279
"Test fragment {} not supported by language of test file {}",
@@ -289,7 +283,18 @@ impl TestArgs {
289283
};
290284
match result {
291285
Err(err) => {
292-
file_status.failure("failed to build stack graph", Some(&err.display_pretty()));
286+
file_status.failure(
287+
"failed to build stack graph",
288+
Some(&format!(
289+
"{}",
290+
err.display_pretty(
291+
&test.path,
292+
source,
293+
lc.sgl.tsg_path(),
294+
lc.sgl.tsg_source(),
295+
)
296+
)),
297+
);
293298
return Err(anyhow!("Failed to build graph for {}", test_path.display()));
294299
}
295300
Ok(_) => {}
@@ -301,13 +306,13 @@ impl TestArgs {
301306
partials.find_minimal_partial_path_set_in_file(
302307
&test.graph,
303308
file,
304-
&(cancellation_flag as &dyn CancellationFlag),
309+
&cancellation_flag.as_ref(),
305310
|g, ps, p| {
306311
db.add_partial_path(g, ps, p);
307312
},
308313
)?;
309314
}
310-
let result = test.run(&mut partials, &mut db, cancellation_flag)?;
315+
let result = test.run(&mut partials, &mut db, cancellation_flag.as_ref())?;
311316
let success = self.handle_result(&result, file_status)?;
312317
if self.output_mode.test(!success) {
313318
let files = test.fragments.iter().map(|f| f.file).collect::<Vec<_>>();
@@ -319,7 +324,7 @@ impl TestArgs {
319324
&mut db,
320325
&|_: &StackGraph, h: &Handle<File>| files.contains(h),
321326
success,
322-
cancellation_flag,
327+
cancellation_flag.as_ref(),
323328
)?;
324329
}
325330
Ok(result)

0 commit comments

Comments
 (0)