Move test helpers into their own folder
This commit is contained in:
parent
5a12fbd927
commit
af83e8034e
8 changed files with 21 additions and 14 deletions
106
cli/src/tests/helpers/allocations.rs
Normal file
106
cli/src/tests/helpers/allocations.rs
Normal 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;
|
||||
}
|
||||
46
cli/src/tests/helpers/fixtures.rs
Normal file
46
cli/src/tests/helpers/fixtures.rs
Normal 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()
|
||||
}
|
||||
4
cli/src/tests/helpers/mod.rs
Normal file
4
cli/src/tests/helpers/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub(super) mod allocations;
|
||||
pub(super) mod fixtures;
|
||||
pub(super) mod random;
|
||||
pub(super) mod scope_sequence;
|
||||
41
cli/src/tests/helpers/random.rs
Normal file
41
cli/src/tests/helpers/random.rs
Normal 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
|
||||
}
|
||||
}
|
||||
97
cli/src/tests/helpers/scope_sequence.rs
Normal file
97
cli/src/tests/helpers/scope_sequence.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue