Skip to content

Commit a2bf7ab

Browse files
committed
test: add analysis-level proptest regression coverage
1 parent 6521f19 commit a2bf7ab

1 file changed

Lines changed: 158 additions & 0 deletions

File tree

crates/ide/src/analysis.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,161 @@ impl Analysis {
218218
self.with_db(|db| code_action::code_action(db, file_id, range, resolve_strategy))
219219
}
220220
}
221+
222+
#[cfg(test)]
223+
mod robustness_tests {
224+
use base_db::{change::Change, source_root::SourceRoot};
225+
use proptest::prelude::*;
226+
use triomphe::Arc;
227+
use utils::{line_index::TextSize, lines::LineEnding};
228+
use vfs::{ChangeKind, ChangedFile, FileId, FileSet, VfsPath};
229+
230+
use super::*;
231+
use crate::{
232+
analysis_host::AnalysisHost,
233+
completion::context::TriggerChar,
234+
folding_ranges::FoldingConfig,
235+
};
236+
237+
const FILE_ID: FileId = FileId(0);
238+
239+
fn ascii_source_strategy() -> impl Strategy<Value = String> {
240+
let char_strategy = prop_oneof![
241+
Just(' '),
242+
Just('\n'),
243+
Just('\t'),
244+
Just('('),
245+
Just(')'),
246+
Just('{'),
247+
Just('}'),
248+
Just(';'),
249+
Just(':'),
250+
Just(','),
251+
Just('.'),
252+
Just('#'),
253+
Just('`'),
254+
Just('_'),
255+
(b'a'..=b'z').prop_map(|byte| byte as char),
256+
(b'A'..=b'Z').prop_map(|byte| byte as char),
257+
(b'0'..=b'9').prop_map(|byte| byte as char),
258+
];
259+
260+
prop::collection::vec(char_strategy, 0..256).prop_map(|chars| chars.into_iter().collect())
261+
}
262+
263+
fn edit_sequence_strategy() -> impl Strategy<Value = Vec<(u16, u8, char)>> {
264+
let char_strategy = prop_oneof![
265+
Just(' '),
266+
Just('\n'),
267+
Just('\t'),
268+
Just('('),
269+
Just(')'),
270+
Just('{'),
271+
Just('}'),
272+
Just(';'),
273+
Just(':'),
274+
Just(','),
275+
Just('.'),
276+
Just('#'),
277+
Just('`'),
278+
Just('_'),
279+
(b'a'..=b'z').prop_map(|byte| byte as char),
280+
(b'A'..=b'Z').prop_map(|byte| byte as char),
281+
(b'0'..=b'9').prop_map(|byte| byte as char),
282+
];
283+
284+
prop::collection::vec((any::<u16>(), 0u8..3, char_strategy), 0..48)
285+
}
286+
287+
fn trigger_strategy() -> impl Strategy<Value = Option<TriggerChar>> {
288+
prop_oneof![
289+
Just(None),
290+
Just(Some(TriggerChar::Dot)),
291+
Just(Some(TriggerChar::OpenParen)),
292+
Just(Some(TriggerChar::Comma)),
293+
Just(Some(TriggerChar::At)),
294+
Just(Some(TriggerChar::Hash)),
295+
Just(Some(TriggerChar::Backtick)),
296+
]
297+
}
298+
299+
fn apply_edit(text: &mut String, position_seed: u16, kind: u8, ch: char) {
300+
match kind {
301+
0 => {
302+
let idx = usize::from(position_seed) % (text.len() + 1);
303+
text.insert(idx, ch);
304+
}
305+
1 => {
306+
if !text.is_empty() {
307+
let idx = usize::from(position_seed) % text.len();
308+
text.remove(idx);
309+
}
310+
}
311+
2 => {
312+
if text.is_empty() {
313+
text.push(ch);
314+
} else {
315+
let idx = usize::from(position_seed) % text.len();
316+
text.remove(idx);
317+
text.insert(idx, ch);
318+
}
319+
}
320+
_ => unreachable!(),
321+
}
322+
}
323+
324+
fn setup_host(text: &str) -> AnalysisHost {
325+
let path = VfsPath::new_virtual_path("/robustness.sv".to_string());
326+
let mut file_set = FileSet::default();
327+
file_set.insert(FILE_ID, path);
328+
329+
let root = SourceRoot::new_local(file_set);
330+
let mut change = Change::new();
331+
change.set_roots(vec![root]);
332+
change.add_changed_file(ChangedFile {
333+
file_id: FILE_ID,
334+
change_kind: ChangeKind::Create(Arc::from(text), LineEnding::Unix),
335+
});
336+
337+
let mut host = AnalysisHost::default();
338+
host.apply_change(change);
339+
host
340+
}
341+
342+
fn update_host(host: &mut AnalysisHost, text: &str) {
343+
let mut change = Change::new();
344+
change.add_changed_file(ChangedFile {
345+
file_id: FILE_ID,
346+
change_kind: ChangeKind::Modify(Arc::from(text), LineEnding::Unix),
347+
});
348+
host.apply_change(change);
349+
}
350+
351+
proptest! {
352+
#[test]
353+
fn analysis_requests_survive_random_ascii_edits(
354+
initial_text in ascii_source_strategy(),
355+
edits in edit_sequence_strategy(),
356+
trigger in trigger_strategy(),
357+
) {
358+
let mut text = initial_text;
359+
let mut host = setup_host(&text);
360+
361+
for (position_seed, kind, ch) in edits {
362+
apply_edit(&mut text, position_seed, kind, ch);
363+
update_host(&mut host, &text);
364+
365+
let analysis = host.make_analysis();
366+
let _ = analysis.parse_diagnostics(FILE_ID)?;
367+
let _ = analysis.document_symbol(FILE_ID)?;
368+
let _ = analysis.folding_ranges(FILE_ID, &FoldingConfig { line_fold_only: false })?;
369+
370+
for offset in [0, text.len() / 2, text.len()] {
371+
let position = FilePosition { file_id: FILE_ID, offset: TextSize::from(offset as u32) };
372+
let _ = analysis.completion_context_with_trigger(position, trigger)?;
373+
let _ = analysis.completions_with_trigger(position, trigger)?;
374+
}
375+
}
376+
}
377+
}
378+
}

0 commit comments

Comments
 (0)