Assert no memory leaks by stubbing malloc/free in the test suite
This commit is contained in:
parent
6105bf9909
commit
196339aaa9
7 changed files with 146 additions and 18 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -502,6 +502,11 @@ name = "smallbitvec"
|
|||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.7.0"
|
||||
|
|
@ -585,6 +590,7 @@ dependencies = [
|
|||
"serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallbitvec 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tree-sitter 0.3.5",
|
||||
]
|
||||
|
||||
|
|
@ -702,6 +708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "225de307c6302bec3898c51ca302fc94a7a1697ef0845fcee6448f33c032249c"
|
||||
"checksum serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "c37ccd6be3ed1fdf419ee848f7c758eb31b054d7cd3ae3600e3bae0adf569811"
|
||||
"checksum smallbitvec 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1764fe2b30ee783bfe3b9b37b2649d8d590b3148bb12e0079715d4d5c673562e"
|
||||
"checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55"
|
||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||
"checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7"
|
||||
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
|
||||
|
|
|
|||
|
|
@ -34,3 +34,6 @@ features = ["preserve_order"]
|
|||
[dependencies.log]
|
||||
version = "0.4.6"
|
||||
features = ["std"]
|
||||
|
||||
[dev-dependencies]
|
||||
spin = "0.5"
|
||||
|
|
|
|||
104
cli/src/tests/allocations.rs
Normal file
104
cli/src/tests/allocations.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#![cfg(test)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use spin::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::os::raw::{c_ulong, c_void};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
struct Allocation(*const c_void);
|
||||
unsafe impl Send for Allocation {}
|
||||
unsafe impl Sync for Allocation {}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AllocationRecorder {
|
||||
enabled: bool,
|
||||
allocation_count: u64,
|
||||
outstanding_allocations: HashMap<Allocation, u64>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref RECORDER: Mutex<AllocationRecorder> = Mutex::new(AllocationRecorder::default());
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn malloc(size: c_ulong) -> *mut c_void;
|
||||
fn calloc(count: c_ulong, size: c_ulong) -> *mut c_void;
|
||||
fn realloc(ptr: *mut c_void, size: c_ulong) -> *mut c_void;
|
||||
fn free(ptr: *mut c_void);
|
||||
}
|
||||
|
||||
pub fn start_recording() {
|
||||
let mut recorder = RECORDER.lock();
|
||||
recorder.enabled = true;
|
||||
recorder.allocation_count = 0;
|
||||
recorder.outstanding_allocations.clear();
|
||||
}
|
||||
|
||||
pub fn stop_recording() {
|
||||
let mut recorder = RECORDER.lock();
|
||||
recorder.enabled = false;
|
||||
|
||||
if !recorder.outstanding_allocations.is_empty() {
|
||||
panic!(
|
||||
"Leaked allocation indices: {:?}",
|
||||
recorder
|
||||
.outstanding_allocations
|
||||
.iter()
|
||||
.map(|e| e.1)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fn record_dealloc(ptr: *mut c_void) {
|
||||
let mut recorder = RECORDER.lock();
|
||||
if recorder.enabled {
|
||||
recorder.outstanding_allocations.remove(&Allocation(ptr));
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn ts_record_malloc(size: c_ulong) -> *const c_void {
|
||||
let result = unsafe { malloc(size) };
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn ts_record_calloc(count: c_ulong, size: c_ulong) -> *const c_void {
|
||||
let result = unsafe { calloc(count, size) };
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn ts_record_realloc(ptr: *mut c_void, size: c_ulong) -> *const c_void {
|
||||
record_dealloc(ptr);
|
||||
let result = unsafe { realloc(ptr, size) };
|
||||
record_alloc(result);
|
||||
result
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn ts_record_free(ptr: *mut c_void) {
|
||||
record_dealloc(ptr);
|
||||
unsafe { free(ptr) };
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn ts_record_allocations_toggle() {
|
||||
let mut recorder = RECORDER.lock();
|
||||
recorder.enabled = !recorder.enabled;
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
use super::allocations;
|
||||
use super::fixtures::{fixtures_dir, get_language, get_test_language};
|
||||
use crate::generate;
|
||||
use crate::test::{parse_tests, print_diff, print_diff_key, TestEntry};
|
||||
use crate::util;
|
||||
use std::fs;
|
||||
use tree_sitter::{LogType, Parser};
|
||||
use tree_sitter::{Language, LogType, Parser};
|
||||
|
||||
const LANGUAGES: &'static [&'static str] = &[
|
||||
"bash",
|
||||
|
|
@ -27,8 +28,6 @@ lazy_static! {
|
|||
|
||||
#[test]
|
||||
fn test_real_language_corpus_files() {
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log1.html");
|
||||
let grammars_dir = fixtures_dir().join("grammars");
|
||||
|
||||
let mut did_fail = false;
|
||||
|
|
@ -44,8 +43,7 @@ fn test_real_language_corpus_files() {
|
|||
let language = get_language(language_name);
|
||||
let corpus_dir = grammars_dir.join(language_name).join("corpus");
|
||||
let test = parse_tests(&corpus_dir).unwrap();
|
||||
parser.set_language(language).unwrap();
|
||||
did_fail |= run_mutation_tests(&mut parser, test);
|
||||
did_fail |= run_mutation_tests(language, test);
|
||||
}
|
||||
|
||||
if did_fail {
|
||||
|
|
@ -55,8 +53,6 @@ fn test_real_language_corpus_files() {
|
|||
|
||||
#[test]
|
||||
fn test_error_corpus_files() {
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log2.html");
|
||||
let corpus_dir = fixtures_dir().join("error_corpus");
|
||||
|
||||
let mut did_fail = false;
|
||||
|
|
@ -74,8 +70,7 @@ fn test_error_corpus_files() {
|
|||
|
||||
let test = parse_tests(&entry.path()).unwrap();
|
||||
let language = get_language(&language_name);
|
||||
parser.set_language(language).unwrap();
|
||||
did_fail |= run_mutation_tests(&mut parser, test);
|
||||
did_fail |= run_mutation_tests(language, test);
|
||||
}
|
||||
|
||||
if did_fail {
|
||||
|
|
@ -85,8 +80,6 @@ fn test_error_corpus_files() {
|
|||
|
||||
#[test]
|
||||
fn test_feature_corpus_files() {
|
||||
let mut log_session = None;
|
||||
let mut parser = get_parser(&mut log_session, "log3.html");
|
||||
let test_grammars_dir = fixtures_dir().join("test_grammars");
|
||||
|
||||
let mut did_fail = false;
|
||||
|
|
@ -132,8 +125,7 @@ fn test_feature_corpus_files() {
|
|||
let c_code = generate_result.unwrap().1;
|
||||
let language = get_test_language(language_name, c_code, &test_path);
|
||||
let test = parse_tests(&corpus_path).unwrap();
|
||||
parser.set_language(language).unwrap();
|
||||
did_fail |= run_mutation_tests(&mut parser, test);
|
||||
did_fail |= run_mutation_tests(language, test);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +134,7 @@ fn test_feature_corpus_files() {
|
|||
}
|
||||
}
|
||||
|
||||
fn run_mutation_tests(parser: &mut Parser, test: TestEntry) -> bool {
|
||||
fn run_mutation_tests(language: Language, test: TestEntry) -> bool {
|
||||
match test {
|
||||
TestEntry::Example {
|
||||
name,
|
||||
|
|
@ -157,23 +149,30 @@ fn run_mutation_tests(parser: &mut Parser, test: TestEntry) -> bool {
|
|||
|
||||
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_utf8(&mut |byte_offset, _| &input[byte_offset..], None)
|
||||
.unwrap();
|
||||
let actual = tree.root_node().to_sexp();
|
||||
drop(tree);
|
||||
drop(parser);
|
||||
if actual != output {
|
||||
print_diff_key();
|
||||
print_diff(&actual, &output);
|
||||
println!("");
|
||||
true
|
||||
} else {
|
||||
allocations::stop_recording();
|
||||
false
|
||||
}
|
||||
}
|
||||
TestEntry::Group { children, .. } => {
|
||||
let mut result = false;
|
||||
for child in children {
|
||||
result |= run_mutation_tests(parser, child);
|
||||
result |= run_mutation_tests(language, child);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
mod allocations;
|
||||
mod corpuses;
|
||||
mod fixtures;
|
||||
mod parser_api;
|
||||
|
|
|
|||
18
lib/build.rs
18
lib/build.rs
|
|
@ -1,6 +1,6 @@
|
|||
extern crate cc;
|
||||
|
||||
use std::env;
|
||||
use std::{env, fs};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn main() {
|
||||
|
|
@ -20,13 +20,27 @@ fn main() {
|
|||
}
|
||||
|
||||
let mut config = cc::Build::new();
|
||||
|
||||
println!("cargo:rerun-if-env-changed=TREE_SITTER_TEST");
|
||||
if env::var("TREE_SITTER_TEST").is_ok() {
|
||||
config.define("TREE_SITTER_TEST", "");
|
||||
}
|
||||
|
||||
let src_path = Path::new("src");
|
||||
|
||||
for entry in fs::read_dir(&src_path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path = src_path.join(entry.file_name());
|
||||
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
|
||||
}
|
||||
|
||||
config
|
||||
.define("UTF8PROC_STATIC", "")
|
||||
.flag_if_supported("-std=c99")
|
||||
.flag_if_supported("-Wno-unused-parameter")
|
||||
.include("include")
|
||||
.include("utf8proc")
|
||||
.file(Path::new("src").join("lib.c"))
|
||||
.file(src_path.join("lib.c"))
|
||||
.compile("tree-sitter");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -855,7 +855,7 @@ char *ts_subtree_string(Subtree self, const TSLanguage *language, bool include_a
|
|||
language, true,
|
||||
include_all, 0, false
|
||||
) + 1;
|
||||
char *result = ts_malloc(size * sizeof(char));
|
||||
char *result = malloc(size * sizeof(char));
|
||||
ts_subtree__write_to_string(self, result, size, language, true, include_all, 0, false);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue