diff --git a/cli/src/main.rs b/cli/src/main.rs index 60867780..94ec290a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -8,7 +8,6 @@ use tree_sitter_cli::{ config, error, generate, highlight, loader, logger, parse, query, test, test_highlight, wasm, web_ui, }; -use tree_sitter_highlight::Highlighter; const BUILD_VERSION: &'static str = env!("CARGO_PKG_VERSION"); const BUILD_SHA: Option<&'static str> = option_env!("BUILD_SHA"); @@ -100,9 +99,6 @@ fn run() -> error::Result<()> { .arg(Arg::with_name("debug").long("debug").short("d")) .arg(Arg::with_name("debug-graph").long("debug-graph").short("D")), ) - .subcommand( - SubCommand::with_name("test-highlight").about("Run a parser's highlighting tests"), - ) .subcommand( SubCommand::with_name("highlight") .about("Highlight a file") @@ -167,38 +163,28 @@ fn run() -> error::Result<()> { let debug = matches.is_present("debug"); let debug_graph = matches.is_present("debug-graph"); let filter = matches.value_of("filter"); - if let Some(language) = loader.languages_at_path(¤t_dir)?.first() { - test::run_tests_at_path( - *language, - ¤t_dir.join("corpus"), - debug, - debug_graph, - filter, - )?; - test::check_queries_at_path(*language, ¤t_dir.join("queries"))?; - } else { - eprintln!("No language found"); - } - } else if matches.subcommand_matches("test-highlight").is_some() { - loader.find_language_configurations_at_path(¤t_dir)?; - let mut highlighter = Highlighter::new(); + let languages = loader.languages_at_path(¤t_dir)?; + let language = languages + .first() + .ok_or_else(|| "No language found".to_string())?; + let test_dir = current_dir.join("test"); - for highlight_test_file in fs::read_dir(current_dir.join("highlight-test"))? { - let test_file_path = highlight_test_file?.path(); - if let Some((language, language_config)) = - loader.language_configuration_for_file_name(&test_file_path)? - { - if let Some(highlight_config) = language_config.highlight_config(language)? { - test_highlight::test_highlight( - &loader, - &mut highlighter, - highlight_config, - fs::read(test_file_path)?.as_slice(), - )?; - } - } else { - return Error::err(format!("No language found for path {:?}", test_file_path)); - } + // Run the corpus tests. Look for them at two paths: `test/corpus` and `corpus`. + let mut test_corpus_dir = test_dir.join("corpus"); + if !test_corpus_dir.is_dir() { + test_corpus_dir = current_dir.join("corpus"); + } + if test_corpus_dir.is_dir() { + test::run_tests_at_path(*language, &test_corpus_dir, debug, debug_graph, filter)?; + } + + // Check that all of the queries are valid. + test::check_queries_at_path(*language, ¤t_dir.join("queries"))?; + + // Run the syntax highlighting tests. + let test_highlight_dir = test_dir.join("highlight"); + if test_highlight_dir.is_dir() { + test_highlight::test_highlights(&loader, &test_highlight_dir)?; } } else if let Some(matches) = matches.subcommand_matches("parse") { let debug = matches.is_present("debug"); diff --git a/cli/src/test_highlight.rs b/cli/src/test_highlight.rs index e7ec41eb..2011af40 100644 --- a/cli/src/test_highlight.rs +++ b/cli/src/test_highlight.rs @@ -1,7 +1,10 @@ use super::error::Result; use crate::loader::Loader; +use ansi_term::Colour; use lazy_static::lazy_static; use regex::Regex; +use std::fs; +use std::path::Path; use tree_sitter::{Language, Parser, Point}; use tree_sitter_highlight::{Highlight, HighlightConfiguration, HighlightEvent, Highlighter}; @@ -19,7 +22,7 @@ pub struct Failure { impl Failure { pub fn message(&self) -> String { let mut result = format!( - "Failed assertion at row: {}, column: {}.\nExpected highlight: '{}'.\nActual highlights: ", + "Failure - row: {}, column: {}, expected highlight '{}', actual highlights: ", self.row, self.column, self.expected_highlight ); if self.actual_highlights.is_empty() { @@ -38,12 +41,58 @@ impl Failure { } } +pub fn test_highlights(loader: &Loader, directory: &Path) -> Result<()> { + let mut failed = false; + let mut highlighter = Highlighter::new(); + + println!("syntax highlighting:"); + for highlight_test_file in fs::read_dir(directory)? { + let highlight_test_file = highlight_test_file?; + let test_file_path = highlight_test_file.path(); + let test_file_name = highlight_test_file.file_name(); + let (language, language_config) = loader + .language_configuration_for_file_name(&test_file_path)? + .ok_or_else(|| format!("No language found for path {:?}", test_file_path))?; + let highlight_config = language_config + .highlight_config(language)? + .ok_or_else(|| format!("No highlighting config found for {:?}", test_file_path))?; + match test_highlight( + &loader, + &mut highlighter, + highlight_config, + fs::read(&test_file_path)?.as_slice(), + ) { + Ok(assertion_count) => { + println!( + " ✓ {} ({} assertions)", + Colour::Green.paint(test_file_name.to_string_lossy().as_ref()), + assertion_count + ); + } + Err(e) => { + println!( + " ✗ {}", + Colour::Red.paint(test_file_name.to_string_lossy().as_ref()) + ); + println!(" {}", e.message()); + failed = true; + } + } + } + + if failed { + Err(String::new().into()) + } else { + Ok(()) + } +} + pub fn test_highlight( loader: &Loader, highlighter: &mut Highlighter, highlight_config: &HighlightConfiguration, source: &[u8], -) -> Result<()> { +) -> Result { // Highlight the file, and parse out all of the highlighting assertions. let highlight_names = loader.highlight_names(); let highlights = get_highlight_positions(loader, highlighter, highlight_config, source)?; @@ -53,7 +102,7 @@ pub fn test_highlight( // actual highlights. let mut i = 0; let mut actual_highlights = Vec::<&String>::new(); - for (position, expected_highlight) in assertions { + for (position, expected_highlight) in &assertions { let mut passed = false; actual_highlights.clear(); @@ -61,7 +110,7 @@ pub fn test_highlight( // The assertions are ordered by position, so skip past all of the highlights that // end at or before this assertion's position. if let Some(highlight) = highlights.get(i) { - if highlight.1 <= position { + if highlight.1 <= *position { i += 1; continue; } @@ -70,7 +119,7 @@ pub fn test_highlight( // position, looking for one that matches the assertion. let mut j = i; while let (false, Some(highlight)) = (passed, highlights.get(j)) { - if highlight.0 > position { + if highlight.0 > *position { break 'highlight_loop; } @@ -79,7 +128,7 @@ pub fn test_highlight( // assertion's position, in order to generate an error message in the event // of a failure. let highlight_name = &highlight_names[(highlight.2).0]; - if *highlight_name == expected_highlight { + if *highlight_name == *expected_highlight { passed = true; break 'highlight_loop; } else { @@ -104,7 +153,7 @@ pub fn test_highlight( } } - Ok(()) + Ok(assertions.len()) } /// Parse the given source code, finding all of the comments that contain diff --git a/cli/src/tests/corpus_test.rs b/cli/src/tests/corpus_test.rs index c10b896f..af4bb6ba 100644 --- a/cli/src/tests/corpus_test.rs +++ b/cli/src/tests/corpus_test.rs @@ -58,7 +58,11 @@ fn test_real_language_corpus_files() { } let language = get_language(language_name); - let corpus_dir = grammars_dir.join(language_name).join("corpus"); + let mut corpus_dir = grammars_dir.join(language_name).join("corpus"); + if !corpus_dir.is_dir() { + corpus_dir = grammars_dir.join(language_name).join("test").join("corpus"); + } + let error_corpus_file = error_corpus_dir.join(&format!("{}_errors.txt", language_name)); let main_tests = parse_tests(&corpus_dir).unwrap(); let error_tests = parse_tests(&error_corpus_file).unwrap_or(TestEntry::default());