Move test helpers into their own folder

This commit is contained in:
Max Brunsfeld 2019-01-25 16:40:26 -08:00
parent 5a12fbd927
commit af83e8034e
8 changed files with 21 additions and 14 deletions

View file

@ -0,0 +1,106 @@
#![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() {
let mut allocation_indices = recorder
.outstanding_allocations
.iter()
.map(|e| e.1)
.collect::<Vec<_>>();
allocation_indices.sort_unstable();
panic!(
"Leaked allocation indices: {:?}",
allocation_indices
);
}
}
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;
}

View file

@ -0,0 +1,46 @@
use crate::loader::Loader;
use std::fs;
use std::path::{Path, PathBuf};
use tree_sitter::Language;
lazy_static! {
static ref ROOT_DIR: PathBuf = [env!("CARGO_MANIFEST_DIR"), ".."].iter().collect();
static ref FIXTURES_DIR: PathBuf = ROOT_DIR.join("test").join("fixtures");
static ref HEADER_DIR: PathBuf = ROOT_DIR.join("lib").join("include");
static ref GRAMMARS_DIR: PathBuf = ROOT_DIR.join("test").join("fixtures").join("grammars");
static ref SCRATCH_DIR: PathBuf = {
let result = ROOT_DIR.join("target").join("scratch");
fs::create_dir_all(&result).unwrap();
result
};
static ref TEST_LOADER: Loader = Loader::new(SCRATCH_DIR.clone());
}
pub fn fixtures_dir<'a>() -> &'static Path {
&FIXTURES_DIR
}
pub fn get_language(name: &str) -> Language {
TEST_LOADER
.load_language_at_path(name, &GRAMMARS_DIR.join(name).join("src"), &HEADER_DIR)
.unwrap()
}
pub fn get_test_language(name: &str, parser_code: String, path: &Path) -> Language {
let parser_c_path = SCRATCH_DIR.join(&format!("{}-parser.c", name));
if !fs::read_to_string(&parser_c_path)
.map(|content| content == parser_code)
.unwrap_or(false)
{
fs::write(&parser_c_path, parser_code).unwrap();
}
let scanner_path = path.join("scanner.c");
let scanner_path = if scanner_path.exists() {
Some(scanner_path)
} else {
None
};
TEST_LOADER
.load_language_from_sources(name, &HEADER_DIR, &parser_c_path, &scanner_path)
.unwrap()
}

View file

@ -0,0 +1,4 @@
pub(super) mod allocations;
pub(super) mod fixtures;
pub(super) mod random;
pub(super) mod scope_sequence;

View file

@ -0,0 +1,41 @@
use rand::distributions::Alphanumeric;
use rand::prelude::{Rng, SeedableRng, SmallRng};
const OPERATORS: &[char] = &[
'+', '-', '<', '>', '(', ')', '*', '/', '&', '|', '!', ',', '.',
];
pub struct Rand(SmallRng);
impl Rand {
pub fn new(seed: usize) -> Self {
Rand(SmallRng::seed_from_u64(seed as u64))
}
pub fn unsigned(&mut self, max: usize) -> usize {
self.0.gen_range(0, max + 1)
}
pub fn words(&mut self, max_count: usize) -> Vec<u8> {
let mut result = Vec::new();
let word_count = self.unsigned(max_count);
for i in 0..word_count {
if i > 0 {
if self.unsigned(5) == 0 {
result.push('\n' as u8);
} else {
result.push(' ' as u8);
}
}
if self.unsigned(3) == 0 {
let index = self.unsigned(OPERATORS.len() - 1);
result.push(OPERATORS[index] as u8);
} else {
for _ in 0..self.unsigned(8) {
result.push(self.0.sample(Alphanumeric) as u8);
}
}
}
result
}
}

View file

@ -0,0 +1,97 @@
use tree_sitter::{Point, Range, Tree};
#[derive(Debug)]
pub struct ScopeSequence(Vec<ScopeStack>);
type ScopeStack = Vec<&'static str>;
impl ScopeSequence {
pub fn new(tree: &Tree) -> Self {
let mut result = ScopeSequence(Vec::new());
let mut scope_stack = Vec::new();
let mut cursor = tree.walk();
let mut visited_children = false;
loop {
let node = cursor.node();
for _ in result.0.len()..node.start_byte() {
result.0.push(scope_stack.clone());
}
if visited_children {
for _ in result.0.len()..node.end_byte() {
result.0.push(scope_stack.clone());
}
scope_stack.pop();
if cursor.goto_next_sibling() {
visited_children = false;
} else if !cursor.goto_parent() {
break;
}
} else {
scope_stack.push(cursor.node().kind());
if !cursor.goto_first_child() {
visited_children = true;
}
}
}
result
}
pub fn check_changes(
&self,
other: &ScopeSequence,
text: &Vec<u8>,
known_changed_ranges: &Vec<Range>,
) -> Result<(), String> {
if self.0.len() != text.len() {
panic!(
"Inconsistent scope sequence: {:?}",
self.0.iter().zip(text.iter().map(|c| *c as char)).collect::<Vec<_>>()
);
}
assert_eq!(self.0.len(), other.0.len());
let mut position = Point { row: 0, column: 0 };
for (i, stack) in self.0.iter().enumerate() {
let other_stack = &other.0[i];
if *stack != *other_stack {
let containing_range = known_changed_ranges
.iter()
.find(|range| range.start_point <= position && position < range.end_point);
if containing_range.is_none() {
let line = &text[(i - position.column)..]
.split(|c| *c == '\n' as u8)
.next()
.unwrap();
return Err(format!(
concat!(
"Position: {}\n",
"Byte offset: {}\n",
"Line: {}\n",
"{}^\n",
"Old scopes: {:?}\n",
"New scopes: {:?}\n",
"Invalidated ranges: {:?}",
),
position,
i,
String::from_utf8_lossy(line),
String::from(" ").repeat(position.column + "Line: ".len()),
stack,
other_stack,
known_changed_ranges,
));
}
}
if text[i] == '\n' as u8 {
position.row += 1;
position.column = 0;
} else {
position.column += 1;
}
}
Ok(())
}
}