#![allow(clippy::unwrap_used)]
use proptest::prelude::*;
use source_lang::{BytePos, LineIndex, SourceId, SourceMap};
fn naive_locate(map: &SourceMap, pos: BytePos) -> Option<(SourceId, BytePos)> {
for (id, file) in map.iter() {
if file.span().contains(pos) {
let local = pos.to_u32() - file.span().start().to_u32();
return Some((id, BytePos::new(local)));
}
}
None
}
fn build(texts: &[String]) -> SourceMap {
let mut map = SourceMap::new();
for (i, text) in texts.iter().enumerate() {
map.add(format!("f{i}"), text.as_str())
.expect("small inputs always fit");
}
map
}
fn space_end(map: &SourceMap) -> u32 {
map.iter()
.map(|(_, f)| f.span().end().to_u32())
.max()
.unwrap_or(0)
}
proptest! {
#[test]
fn locate_matches_naive_resolver(
texts in prop::collection::vec(".{0,40}", 0..40),
raw in any::<u32>(),
) {
let map = build(&texts);
let span = space_end(&map);
let pos = BytePos::new(raw % (span + 8));
prop_assert_eq!(map.locate(pos), naive_locate(&map, pos));
}
#[test]
fn located_position_round_trips_to_its_source(
texts in prop::collection::vec(".{0,40}", 0..40),
raw in any::<u32>(),
) {
let map = build(&texts);
let span = space_end(&map);
prop_assume!(span > 0);
let pos = BytePos::new(raw % span); let (id, local) = map.locate(pos).expect("in-range position resolves");
let file = map.source(id).expect("located id is valid");
prop_assert_eq!(file.span().start().to_u32() + local.to_u32(), pos.to_u32());
prop_assert!((local.to_u32() as usize) < file.text().len());
}
#[test]
fn source_ranges_never_overlap(
texts in prop::collection::vec(".{0,40}", 0..40),
raw in any::<u32>(),
) {
let map = build(&texts);
let mut prev_end = 0u32;
for (_, file) in map.iter() {
prop_assert!(file.span().start().to_u32() >= prev_end);
prev_end = file.span().end().to_u32();
}
let span = space_end(&map);
let pos = BytePos::new(raw % (span + 8));
let hits = map.iter().filter(|(_, f)| f.span().contains(pos)).count();
prop_assert!(hits <= 1);
}
#[test]
fn line_col_matches_per_file_resolution(
texts in prop::collection::vec("(?s).{0,40}", 0..40),
raw in any::<u32>(),
) {
let map = build(&texts);
let span = space_end(&map);
prop_assume!(span > 0);
let pos = BytePos::new(raw % span);
let got = map.line_col(pos).expect("in-range position resolves");
let (id, local) = naive_locate(&map, pos).expect("same in-range position");
let file = map.source(id).expect("located id is valid");
let expected = LineIndex::new(file.text()).line_col(local);
prop_assert_eq!(got, (id, expected));
}
#[test]
fn ids_are_unique_and_stable(
texts in prop::collection::vec(".{0,40}", 0..40),
) {
let mut map = SourceMap::new();
let mut returned = Vec::new();
for (i, text) in texts.iter().enumerate() {
returned.push(map.add(format!("f{i}"), text.as_str()).unwrap());
}
let iterated: Vec<SourceId> = map.iter().map(|(id, _)| id).collect();
prop_assert_eq!(&returned, &iterated);
let indices: Vec<u32> = iterated.iter().map(|id| id.to_u32()).collect();
let expected: Vec<u32> = (0..map.len() as u32).collect();
prop_assert_eq!(indices, expected);
}
}