diff --git a/cli/src/tests/corpus_test.rs b/cli/src/tests/corpus_test.rs index 3a861b5c..700e62a2 100644 --- a/cli/src/tests/corpus_test.rs +++ b/cli/src/tests/corpus_test.rs @@ -12,20 +12,6 @@ use tree_sitter::{allocations, LogType, Node, Parser, Tree}; const EDIT_COUNT: usize = 3; const TRIAL_COUNT: usize = 10; -const LANGUAGES: &'static [&'static str] = &[ - "bash", - "c", - "cpp", - "embedded-template", - "go", - "html", - "javascript", - "json", - "php", - "python", - "ruby", - "rust", -]; lazy_static! { static ref LOG_ENABLED: bool = env::var("TREE_SITTER_TEST_ENABLE_LOG").is_ok(); @@ -36,7 +22,11 @@ lazy_static! { .map(|s| usize::from_str_radix(&s, 10).unwrap()) .ok(); pub static ref SEED: usize = env::var("TREE_SITTER_TEST_SEED") - .map(|s| usize::from_str_radix(&s, 10).unwrap()) + .map(|s| { + let seed = usize::from_str_radix(&s, 10).unwrap(); + eprintln!("\n\nRandom seed: {}\n", *SEED); + seed + }) .unwrap_or( time::SystemTime::now() .duration_since(time::UNIX_EPOCH) @@ -46,40 +36,95 @@ lazy_static! { } #[test] -fn test_real_language_corpus_files() { - eprintln!("\n\nRandom seed: {}\n", *SEED); +fn test_bash_corpus() { + test_language_corpus("bash"); +} + +#[test] +fn test_c_corpus() { + test_language_corpus("c"); +} + +#[test] +fn test_cpp_corpus() { + test_language_corpus("cpp"); +} + +#[test] +fn test_embedded_template_corpus() { + test_language_corpus("embedded-template"); +} + +#[test] +fn test_go_corpus() { + test_language_corpus("go"); +} + +#[test] +fn test_html_corpus() { + test_language_corpus("html"); +} + +#[test] +fn test_javascript_corpus() { + test_language_corpus("javascript"); +} + +#[test] +fn test_json_corpus() { + test_language_corpus("json"); +} + +#[test] +fn test_php_corpus() { + test_language_corpus("php"); +} + +#[test] +fn test_python_corpus() { + test_language_corpus("python"); +} + +#[test] +fn test_ruby_corpus() { + test_language_corpus("ruby"); +} + +#[test] +fn test_rust_corpus() { + test_language_corpus("rust"); +} + +fn test_language_corpus(language_name: &str) { + if let Some(language_filter) = LANGUAGE_FILTER.as_ref() { + if language_filter != language_name { + return; + } + } + let grammars_dir = fixtures_dir().join("grammars"); let error_corpus_dir = fixtures_dir().join("error_corpus"); let mut failure_count = 0; - for language_name in LANGUAGES.iter().cloned() { - if let Some(filter) = LANGUAGE_FILTER.as_ref() { - if language_name != filter.as_str() { - continue; - } - } - let language = get_language(language_name); - 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 language = get_language(language_name); + 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()); - let mut tests = flatten_tests(main_tests); - tests.extend(flatten_tests(error_tests)); + 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()); + let mut tests = flatten_tests(main_tests); + tests.extend(flatten_tests(error_tests)); - if !tests.is_empty() { - eprintln!("language: {:?}", language_name); - } + for (example_name, input, expected_output, has_fields) in tests { + println!(" {} example - {}", language_name, example_name); - for (example_name, input, expected_output, has_fields) in tests { - eprintln!(" example: {:?}", example_name); - - if TRIAL_FILTER.map_or(true, |t| t == 0) { - allocations::start_recording(); + let trial = 0; + if TRIAL_FILTER.map_or(true, |t| t == trial) { + let passed = allocations::record(|| { let mut log_session = None; let mut parser = get_parser(&mut log_session, "log.html"); parser.set_language(language).unwrap(); @@ -88,28 +133,36 @@ fn test_real_language_corpus_files() { if !has_fields { actual_output = strip_sexp_fields(actual_output); } - drop(tree); - drop(parser); - if actual_output != expected_output { + if actual_output == expected_output { + true + } else { + println!( + "Incorrect initial parse for {} - {}", + language_name, example_name, + ); print_diff_key(); print_diff(&actual_output, &expected_output); println!(""); - failure_count += 1; - continue; + false } - allocations::stop_recording(); + }); + + if !passed { + failure_count += 1; + continue; } + } - let mut parser = Parser::new(); - parser.set_language(language).unwrap(); - let tree = parser.parse(&input, None).unwrap(); - drop(parser); + let mut parser = Parser::new(); + parser.set_language(language).unwrap(); + let tree = parser.parse(&input, None).unwrap(); + drop(parser); - for trial in 1..=TRIAL_COUNT { - if TRIAL_FILTER.map_or(true, |filter| filter == trial) { - let mut rand = Rand::new(*SEED + trial); + for trial in 1..=TRIAL_COUNT { + if TRIAL_FILTER.map_or(true, |filter| filter == trial) { + let mut rand = Rand::new(*SEED + trial); - allocations::start_recording(); + let passed = allocations::record(|| { let mut log_session = None; let mut parser = get_parser(&mut log_session, "log.html"); parser.set_language(language).unwrap(); @@ -140,8 +193,7 @@ fn test_real_language_corpus_files() { "\nUnexpected scope change in trial {}\n{}\n\n", trial, message ); - failure_count += 1; - break; + return false; } // Undo all of the edits and re-parse again. @@ -168,8 +220,7 @@ fn test_real_language_corpus_files() { print_diff_key(); print_diff(&actual_output, &expected_output); println!(""); - failure_count += 1; - break; + return false; } // Check that the edited tree is consistent. @@ -179,21 +230,22 @@ fn test_real_language_corpus_files() { "Unexpected scope change in trial {}\n{}\n\n", trial, message ); - failure_count += 1; - break; + return false; } - drop(tree); - drop(tree2); - drop(tree3); - drop(parser); - allocations::stop_recording(); + true + }); + + if !passed { + failure_count += 1; + break; } } } } + if failure_count > 0 { - panic!("{} corpus tests failed", failure_count); + panic!("{} {} corpus tests failed", failure_count, language_name); } } @@ -271,26 +323,29 @@ fn test_feature_corpus_files() { for (name, input, expected_output, has_fields) in tests { eprintln!(" example: {:?}", name); - allocations::start_recording(); - let mut log_session = None; - let mut parser = get_parser(&mut log_session, "log.html"); - parser.set_language(language).unwrap(); - let tree = parser.parse(&input, None).unwrap(); - let mut actual_output = tree.root_node().to_sexp(); - if !has_fields { - actual_output = strip_sexp_fields(actual_output); - } + let passed = allocations::record(|| { + let mut log_session = None; + let mut parser = get_parser(&mut log_session, "log.html"); + parser.set_language(language).unwrap(); + let tree = parser.parse(&input, None).unwrap(); + let mut actual_output = tree.root_node().to_sexp(); + if !has_fields { + actual_output = strip_sexp_fields(actual_output); + } + if actual_output == expected_output { + true + } else { + print_diff_key(); + print_diff(&actual_output, &expected_output); + println!(""); + false + } + }); - drop(tree); - drop(parser); - if actual_output != expected_output { - print_diff_key(); - print_diff(&actual_output, &expected_output); - println!(""); + if !passed { failure_count += 1; continue; } - allocations::stop_recording(); } } } @@ -381,7 +436,12 @@ fn get_parser(session: &mut Option, log_filename: &str) -> Par } fn flatten_tests(test: TestEntry) -> Vec<(String, Vec, String, bool)> { - fn helper(test: TestEntry, prefix: &str, result: &mut Vec<(String, Vec, String, bool)>) { + fn helper( + test: TestEntry, + is_root: bool, + prefix: &str, + result: &mut Vec<(String, Vec, String, bool)>, + ) { match test { TestEntry::Example { mut name, @@ -403,17 +463,17 @@ fn flatten_tests(test: TestEntry) -> Vec<(String, Vec, String, bool)> { TestEntry::Group { mut name, children, .. } => { - if !prefix.is_empty() { + if !is_root && !prefix.is_empty() { name.insert_str(0, " - "); name.insert_str(0, prefix); } for child in children { - helper(child, &name, result); + helper(child, false, &name, result); } } } } let mut result = Vec::new(); - helper(test, "", &mut result); + helper(test, true, "", &mut result); result } diff --git a/cli/src/tests/parser_test.rs b/cli/src/tests/parser_test.rs index a24ed4bb..909fe8b3 100644 --- a/cli/src/tests/parser_test.rs +++ b/cli/src/tests/parser_test.rs @@ -596,23 +596,7 @@ fn test_parsing_with_a_timeout() { let mut parser = Parser::new(); parser.set_language(get_language("json")).unwrap(); - // Parse an infinitely-long array, but pause after 100 microseconds of processing. - parser.set_timeout_micros(100); - let start_time = time::Instant::now(); - let tree = parser.parse_with( - &mut |offset, _| { - if offset == 0 { - b" [" - } else { - b",0" - } - }, - None, - ); - assert!(tree.is_none()); - assert!(start_time.elapsed().as_micros() < 500); - - // Continue parsing, but pause after 300 microseconds of processing. + // Parse an infinitely-long array, but pause after 1ms of processing. parser.set_timeout_micros(1000); let start_time = time::Instant::now(); let tree = parser.parse_with( @@ -626,9 +610,25 @@ fn test_parsing_with_a_timeout() { None, ); assert!(tree.is_none()); - assert!(start_time.elapsed().as_micros() > 500); assert!(start_time.elapsed().as_micros() < 2000); + // Continue parsing, but pause after 1 ms of processing. + parser.set_timeout_micros(5000); + let start_time = time::Instant::now(); + let tree = parser.parse_with( + &mut |offset, _| { + if offset == 0 { + b" [" + } else { + b",0" + } + }, + None, + ); + assert!(tree.is_none()); + assert!(start_time.elapsed().as_micros() > 100); + assert!(start_time.elapsed().as_micros() < 10000); + // Finish parsing parser.set_timeout_micros(0); let tree = parser diff --git a/lib/binding_rust/allocations.rs b/lib/binding_rust/allocations.rs index 9e7a3642..1e017471 100644 --- a/lib/binding_rust/allocations.rs +++ b/lib/binding_rust/allocations.rs @@ -1,8 +1,8 @@ -use lazy_static::lazy_static; use spin::Mutex; -use std::collections::HashMap; -use std::env; -use std::os::raw::{c_ulong, c_void}; +use std::{ + collections::HashMap, + os::raw::{c_ulong, c_void}, +}; #[derive(Debug, PartialEq, Eq, Hash)] struct Allocation(*const c_void); @@ -16,8 +16,8 @@ struct AllocationRecorder { outstanding_allocations: HashMap, } -lazy_static! { - static ref RECORDER: Mutex = Mutex::new(AllocationRecorder::default()); +thread_local! { + static RECORDER: Mutex = Default::default(); } extern "C" { @@ -27,55 +27,55 @@ extern "C" { fn free(ptr: *mut c_void); } -pub fn start_recording() { - let mut recorder = RECORDER.lock(); - recorder.allocation_count = 0; - recorder.outstanding_allocations.clear(); - - if env::var("RUST_TEST_THREADS").map_or(false, |s| s == "1") { +pub fn record(f: impl FnOnce() -> T) -> T { + RECORDER.with(|recorder| { + let mut recorder = recorder.lock(); recorder.enabled = true; - } else { - panic!("This test must be run with RUST_TEST_THREADS=1. Use script/test."); - } -} + recorder.allocation_count = 0; + recorder.outstanding_allocations.clear(); + }); -pub fn stop_recording() { - let mut recorder = RECORDER.lock(); - recorder.enabled = false; + let value = f(); - if !recorder.outstanding_allocations.is_empty() { - let mut allocation_indices = recorder + let outstanding_allocation_indices = RECORDER.with(|recorder| { + let mut recorder = recorder.lock(); + recorder.enabled = false; + recorder.allocation_count = 0; + recorder .outstanding_allocations - .iter() + .drain() .map(|e| e.1) - .collect::>(); - allocation_indices.sort_unstable(); - panic!("Leaked allocation indices: {:?}", allocation_indices); + .collect::>() + }); + if !outstanding_allocation_indices.is_empty() { + panic!( + "Leaked allocation indices: {:?}", + outstanding_allocation_indices + ); } -} - -pub fn record(f: impl FnOnce()) { - start_recording(); - f(); - stop_recording(); + value } fn record_alloc(ptr: *mut c_void) { - let mut recorder = RECORDER.lock(); - if recorder.enabled { - let count = recorder.allocation_count; - recorder.allocation_count += 1; - recorder - .outstanding_allocations - .insert(Allocation(ptr), count); - } + RECORDER.with(|recorder| { + let mut recorder = recorder.lock(); + if recorder.enabled { + let count = recorder.allocation_count; + recorder.allocation_count += 1; + recorder + .outstanding_allocations + .insert(Allocation(ptr), count); + } + }); } fn record_dealloc(ptr: *mut c_void) { - let mut recorder = RECORDER.lock(); - if recorder.enabled { - recorder.outstanding_allocations.remove(&Allocation(ptr)); - } + RECORDER.with(|recorder| { + let mut recorder = recorder.lock(); + if recorder.enabled { + recorder.outstanding_allocations.remove(&Allocation(ptr)); + } + }); } #[no_mangle] @@ -111,8 +111,10 @@ pub unsafe extern "C" fn ts_record_free(ptr: *mut c_void) { #[no_mangle] pub extern "C" fn ts_toggle_allocation_recording(enabled: bool) -> bool { - let mut recorder = RECORDER.lock(); - let was_enabled = recorder.enabled; - recorder.enabled = enabled; - was_enabled + RECORDER.with(|recorder| { + let mut recorder = recorder.lock(); + let was_enabled = recorder.enabled; + recorder.enabled = enabled; + was_enabled + }) } diff --git a/script/test b/script/test index dfb29da7..0fc340cc 100755 --- a/script/test +++ b/script/test @@ -31,13 +31,11 @@ OPTIONS EOF } -export RUST_TEST_THREADS=1 export RUST_BACKTRACE=full mode=normal test_flags="-p tree-sitter-cli" - while getopts "adDghl:e:s:t:" option; do case ${option} in h)