feat(cli): add test listing and allow users to parse a specific test number
This commit is contained in:
parent
33045ee898
commit
577d333849
2 changed files with 161 additions and 17 deletions
|
|
@ -195,6 +195,9 @@ struct Parse {
|
|||
pub open_log: bool,
|
||||
#[arg(long, help = "The path to an alternative config.json file")]
|
||||
pub config_path: Option<PathBuf>,
|
||||
#[arg(long, short = 'n', help = "Parse the contents of a specific test")]
|
||||
#[clap(conflicts_with = "paths", conflicts_with = "paths_file")]
|
||||
pub test_number: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -410,6 +413,8 @@ fn run() -> Result<()> {
|
|||
let current_dir = env::current_dir().unwrap();
|
||||
let mut loader = loader::Loader::new()?;
|
||||
|
||||
let color = env::var("NO_COLOR").map_or(true, |v| v != "1");
|
||||
|
||||
match command {
|
||||
Commands::InitConfig(_) => {
|
||||
if let Ok(Some(config_path)) = Config::find_config_file() {
|
||||
|
|
@ -576,7 +581,22 @@ fn run() -> Result<()> {
|
|||
|
||||
let timeout = parse_options.timeout.unwrap_or_default();
|
||||
|
||||
let paths = collect_paths(parse_options.paths_file.as_deref(), parse_options.paths)?;
|
||||
let (paths, language) = if let Some(target_test) = parse_options.test_number {
|
||||
let (test_path, language_names) = test::get_tmp_test_file(target_test, color)?;
|
||||
let languages = loader.languages_at_path(¤t_dir)?;
|
||||
let language = languages
|
||||
.iter()
|
||||
.find(|(_, n)| language_names.contains(&Box::from(n.as_str())))
|
||||
.map(|(l, _)| l.clone());
|
||||
let paths =
|
||||
collect_paths(None, Some(vec![test_path.to_str().unwrap().to_owned()]))?;
|
||||
(paths, language)
|
||||
} else {
|
||||
(
|
||||
collect_paths(parse_options.paths_file.as_deref(), parse_options.paths)?,
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
let max_path_length = paths.iter().map(|p| p.chars().count()).max().unwrap_or(0);
|
||||
let mut has_error = false;
|
||||
|
|
@ -586,11 +606,14 @@ fn run() -> Result<()> {
|
|||
let should_track_stats = parse_options.stat;
|
||||
let mut stats = parse::Stats::default();
|
||||
|
||||
for path in paths {
|
||||
for path in &paths {
|
||||
let path = Path::new(&path);
|
||||
|
||||
let language =
|
||||
loader.select_language(path, ¤t_dir, parse_options.scope.as_deref())?;
|
||||
let language = if let Some(ref language) = language {
|
||||
language.clone()
|
||||
} else {
|
||||
loader.select_language(path, ¤t_dir, parse_options.scope.as_deref())?
|
||||
};
|
||||
parser
|
||||
.set_language(&language)
|
||||
.context("incompatible language")?;
|
||||
|
|
@ -645,8 +668,6 @@ fn run() -> Result<()> {
|
|||
env::set_var("TREE_SITTER_DEBUG", "1");
|
||||
}
|
||||
|
||||
let color = env::var("NO_COLOR").map_or(true, |v| v != "1");
|
||||
|
||||
loader.use_debug_build(test_options.debug_build);
|
||||
|
||||
let mut parser = Parser::new();
|
||||
|
|
@ -683,6 +704,7 @@ fn run() -> Result<()> {
|
|||
open_log: test_options.open_log,
|
||||
languages: languages.iter().map(|(l, n)| (n.as_str(), l)).collect(),
|
||||
color,
|
||||
test_num: 1,
|
||||
};
|
||||
|
||||
test::run_tests_at_path(&mut parser, &mut opts)?;
|
||||
|
|
|
|||
144
cli/src/test.rs
144
cli/src/test.rs
|
|
@ -1,5 +1,5 @@
|
|||
use std::{
|
||||
collections::BTreeMap,
|
||||
collections::{BTreeMap, HashSet},
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
io::{self, Write},
|
||||
|
|
@ -105,6 +105,7 @@ pub struct TestOptions<'a> {
|
|||
pub open_log: bool,
|
||||
pub languages: BTreeMap<&'a str, &'a Language>,
|
||||
pub color: bool,
|
||||
pub test_num: usize,
|
||||
}
|
||||
|
||||
pub fn run_tests_at_path(parser: &mut Parser, opts: &mut TestOptions) -> Result<()> {
|
||||
|
|
@ -186,6 +187,65 @@ pub fn run_tests_at_path(parser: &mut Parser, opts: &mut TestOptions) -> Result<
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn get_test_info<'test>(
|
||||
test_entry: &'test TestEntry,
|
||||
target_test: u32,
|
||||
test_num: &mut u32,
|
||||
) -> Option<(&'test str, &'test [u8], Vec<Box<str>>)> {
|
||||
match test_entry {
|
||||
TestEntry::Example {
|
||||
name,
|
||||
input,
|
||||
attributes,
|
||||
..
|
||||
} => {
|
||||
if *test_num == target_test {
|
||||
return Some((name, input, attributes.languages.clone()));
|
||||
} else {
|
||||
*test_num += 1;
|
||||
}
|
||||
}
|
||||
TestEntry::Group { children, .. } => {
|
||||
for child in children {
|
||||
if let Some((name, input, languages)) = get_test_info(child, target_test, test_num)
|
||||
{
|
||||
return Some((name, input, languages));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Writes the input of `target_test` to a temporary file and returns the path
|
||||
pub fn get_tmp_test_file(target_test: u32, color: bool) -> Result<(PathBuf, Vec<Box<str>>)> {
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
let test_dir = current_dir.join("test").join("corpus");
|
||||
|
||||
// Get the input of the target test
|
||||
let test_entry = parse_tests(&test_dir)?;
|
||||
let mut test_num = 0;
|
||||
let Some((test_name, test_contents, languages)) =
|
||||
get_test_info(&test_entry, target_test - 1, &mut test_num)
|
||||
else {
|
||||
return Err(anyhow!("Failed to fetch contents of test #{target_test}"));
|
||||
};
|
||||
|
||||
// Write the test contents to a temporary file
|
||||
let test_path = std::env::temp_dir().join(".tree-sitter-test");
|
||||
let mut test_file = std::fs::File::create(&test_path)?;
|
||||
test_file.write_all(test_contents)?;
|
||||
|
||||
println!(
|
||||
"{target_test}. {}\n",
|
||||
opt_color(color, Colour::Green, test_name)
|
||||
);
|
||||
|
||||
Ok((test_path, languages))
|
||||
}
|
||||
|
||||
pub fn check_queries_at_path(language: &Language, path: &Path) -> Result<()> {
|
||||
if path.exists() {
|
||||
for entry in WalkDir::new(path)
|
||||
|
|
@ -253,6 +313,7 @@ pub fn opt_color(use_color: bool, color: ansi_term::Colour, text: &str) -> Strin
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn run_tests(
|
||||
parser: &mut Parser,
|
||||
test_entry: TestEntry,
|
||||
|
|
@ -275,12 +336,20 @@ fn run_tests(
|
|||
print!("{}", " ".repeat(indent_level as usize));
|
||||
|
||||
if attributes.skip {
|
||||
println!(" {}", opt_color(opts.color, Colour::Yellow, &name));
|
||||
println!(
|
||||
"{:>3}. {}",
|
||||
opts.test_num,
|
||||
opt_color(opts.color, Colour::Yellow, &name),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if !attributes.platform {
|
||||
println!(" {}", opt_color(opts.color, Colour::Purple, &name));
|
||||
println!(
|
||||
"{:>3}. {}",
|
||||
opts.test_num,
|
||||
opt_color(opts.color, Colour::Purple, &name)
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
|
|
@ -296,9 +365,17 @@ fn run_tests(
|
|||
|
||||
if attributes.error {
|
||||
if tree.root_node().has_error() {
|
||||
println!(" {}", opt_color(opts.color, Colour::Green, &name));
|
||||
println!(
|
||||
"{:>3}. {}",
|
||||
opts.test_num,
|
||||
opt_color(opts.color, Colour::Green, &name)
|
||||
);
|
||||
} else {
|
||||
println!(" {}", opt_color(opts.color, Colour::Red, &name));
|
||||
println!(
|
||||
"{:>3}. {}",
|
||||
opts.test_num,
|
||||
opt_color(opts.color, Colour::Red, &name)
|
||||
);
|
||||
}
|
||||
|
||||
if attributes.fail_fast {
|
||||
|
|
@ -311,7 +388,11 @@ fn run_tests(
|
|||
}
|
||||
|
||||
if actual == output {
|
||||
println!("✓ {}", opt_color(opts.color, Colour::Green, &name));
|
||||
println!(
|
||||
"{:>3}. ✓ {}",
|
||||
opts.test_num,
|
||||
opt_color(opts.color, Colour::Green, &name),
|
||||
);
|
||||
if opts.update {
|
||||
let input = String::from_utf8(input.clone()).unwrap();
|
||||
let output = format_sexp(&output, 0);
|
||||
|
|
@ -353,10 +434,18 @@ fn run_tests(
|
|||
header_delim_len,
|
||||
divider_delim_len,
|
||||
));
|
||||
println!("✓ {}", opt_color(opts.color, Colour::Blue, &name));
|
||||
println!(
|
||||
"{:>3}. ✓ {}",
|
||||
opts.test_num,
|
||||
opt_color(opts.color, Colour::Blue, &name)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
println!("✗ {}", opt_color(opts.color, Colour::Red, &name));
|
||||
println!(
|
||||
"{:>3}. ✗ {}",
|
||||
opts.test_num,
|
||||
opt_color(opts.color, Colour::Red, &name)
|
||||
);
|
||||
}
|
||||
failures.push((name.clone(), actual, output.clone()));
|
||||
|
||||
|
|
@ -372,34 +461,51 @@ fn run_tests(
|
|||
}
|
||||
}
|
||||
}
|
||||
opts.test_num += 1;
|
||||
}
|
||||
TestEntry::Group {
|
||||
name,
|
||||
mut children,
|
||||
file_path,
|
||||
} => {
|
||||
children.retain(|child| {
|
||||
if let TestEntry::Example { name, .. } = child {
|
||||
// track which tests are being skipped to maintain consistent numbering while using
|
||||
// filters
|
||||
let mut skipped_tests = HashSet::new();
|
||||
let mut advance_counter = opts.test_num;
|
||||
children.retain(|child| match child {
|
||||
TestEntry::Example { name, .. } => {
|
||||
if let Some(filter) = opts.filter {
|
||||
if !name.contains(filter) {
|
||||
skipped_tests.insert(advance_counter);
|
||||
advance_counter += 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(include) = &opts.include {
|
||||
if !include.is_match(name) {
|
||||
skipped_tests.insert(advance_counter);
|
||||
advance_counter += 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(exclude) = &opts.exclude {
|
||||
if exclude.is_match(name) {
|
||||
skipped_tests.insert(advance_counter);
|
||||
advance_counter += 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
advance_counter += 1;
|
||||
true
|
||||
}
|
||||
TestEntry::Group { .. } => {
|
||||
advance_counter += count_subtests(child);
|
||||
true
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
if children.is_empty() {
|
||||
opts.test_num = advance_counter;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
|
|
@ -412,6 +518,11 @@ fn run_tests(
|
|||
|
||||
indent_level += 1;
|
||||
for child in children {
|
||||
if let TestEntry::Example { .. } = child {
|
||||
while skipped_tests.remove(&opts.test_num) {
|
||||
opts.test_num += 1;
|
||||
}
|
||||
}
|
||||
if !run_tests(
|
||||
parser,
|
||||
child,
|
||||
|
|
@ -426,6 +537,8 @@ fn run_tests(
|
|||
}
|
||||
}
|
||||
|
||||
opts.test_num += skipped_tests.len();
|
||||
|
||||
if let Some(file_path) = file_path {
|
||||
if opts.update && failures.len() - failure_count > 0 {
|
||||
write_tests(&file_path, corrected_entries)?;
|
||||
|
|
@ -437,6 +550,15 @@ fn run_tests(
|
|||
Ok(true)
|
||||
}
|
||||
|
||||
fn count_subtests(test_entry: &TestEntry) -> usize {
|
||||
match test_entry {
|
||||
TestEntry::Example { .. } => 1,
|
||||
TestEntry::Group { children, .. } => children
|
||||
.iter()
|
||||
.fold(0, |count, child| count + count_subtests(child)),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_tests(
|
||||
file_path: &Path,
|
||||
corrected_entries: &[(String, String, String, usize, usize)],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue