use badness::incremental::{IncrementalDatabase, QueryKind};
fn parse_count(db: &IncrementalDatabase) -> usize {
db.query_log()
.iter()
.filter(|entry| entry.kind == QueryKind::ParsedDocument)
.count()
}
fn signatures_count(db: &IncrementalDatabase) -> usize {
db.query_log()
.iter()
.filter(|entry| entry.kind == QueryKind::DocumentSignatures)
.count()
}
fn bib_parse_count(db: &IncrementalDatabase) -> usize {
db.query_log()
.iter()
.filter(|entry| entry.kind == QueryKind::ParsedBibDocument)
.count()
}
fn bib_model_count(db: &IncrementalDatabase) -> usize {
db.query_log()
.iter()
.filter(|entry| entry.kind == QueryKind::BibSemanticModel)
.count()
}
fn scanned_commands(
db: &IncrementalDatabase,
file: badness::incremental::SourceFile,
) -> Vec<String> {
let mut names: Vec<String> = db
.document_signatures(file)
.command_names()
.map(str::to_string)
.collect();
names.sort();
names
}
#[test]
fn parsed_document_is_memoized() {
let db = IncrementalDatabase::default();
let file = db.add_file("\\section{Hi}\n");
let _ = db.parsed_tree(file);
let _ = db.parsed_tree(file);
let _ = db.parse_diagnostics(file);
assert_eq!(parse_count(&db), 1);
}
#[test]
fn editing_text_reparses() {
let mut db = IncrementalDatabase::default();
let file = db.add_file("a\n");
let _ = db.parsed_tree(file);
assert_eq!(parse_count(&db), 1);
db.set_file_text(file, "b\n");
let _ = db.parsed_tree(file);
assert_eq!(parse_count(&db), 2);
}
#[test]
fn upsert_unchanged_text_does_not_reparse() {
let mut db = IncrementalDatabase::default();
let path = std::path::Path::new("/tmp/doc.tex");
let file = db.upsert_file(path, "x\n".to_string());
let _ = db.parsed_tree(file);
assert_eq!(parse_count(&db), 1);
let same = db.upsert_file(path, "x\n".to_string());
assert!(same == file);
let _ = db.parsed_tree(same);
assert_eq!(parse_count(&db), 1);
let changed = db.upsert_file(path, "y\n".to_string());
assert!(changed == file);
let _ = db.parsed_tree(changed);
assert_eq!(parse_count(&db), 2);
}
#[test]
fn cached_tree_is_lossless() {
let db = IncrementalDatabase::default();
let input = "\\section{Hi}\n\nbody $x^2$ % c\n";
let file = db.add_file(input);
assert_eq!(db.parsed_tree(file).to_string(), input);
}
#[test]
fn remove_file_stops_tracking() {
let mut db = IncrementalDatabase::default();
let path = std::path::Path::new("/tmp/doc.tex");
let file = db.upsert_file(path, "x\n".to_string());
assert!(db.lookup_file(path) == Some(file));
assert!(db.remove_file(path) == Some(file));
assert!(db.lookup_file(path).is_none());
assert!(db.remove_file(path).is_none());
let reopened = db.upsert_file(path, "x\n".to_string());
assert!(reopened != file);
assert!(db.lookup_file(path) == Some(reopened));
}
#[test]
fn snapshot_reads_cached_parse() {
let mut db = IncrementalDatabase::default();
let path = std::path::Path::new("/tmp/snap.tex");
let file = db.upsert_file(path, "\\emph{hi}\n".to_string());
let _ = db.parsed_tree(file);
let snap = db.snapshot();
let snap_file = snap.lookup_file(path).expect("tracked file");
assert!(snap_file == file);
assert_eq!(snap.file_text(file), "\\emph{hi}\n");
assert!(snap.parse_diagnostics(file).is_empty());
assert_eq!(snap.parsed_tree(file).to_string(), "\\emph{hi}\n");
}
#[test]
fn document_signatures_is_memoized() {
let db = IncrementalDatabase::default();
let file = db.add_file("\\newcommand{\\foo}{x}\n");
let _ = db.document_signatures(file);
let _ = db.document_signatures(file);
let _ = db.document_signatures(file);
assert_eq!(signatures_count(&db), 1);
assert_eq!(scanned_commands(&db, file), vec!["foo".to_string()]);
}
#[test]
fn editing_definitions_rebuilds_signatures() {
let mut db = IncrementalDatabase::default();
let file = db.add_file("\\newcommand{\\foo}{x}\n");
assert_eq!(scanned_commands(&db, file), vec!["foo".to_string()]);
assert_eq!(signatures_count(&db), 1);
db.set_file_text(file, "\\newcommand{\\foo}{x}\n\\newcommand{\\bar}{y}\n");
assert_eq!(
scanned_commands(&db, file),
vec!["bar".to_string(), "foo".to_string()]
);
assert_eq!(signatures_count(&db), 2);
}
#[test]
fn prose_edit_yields_equal_signatures() {
let db = IncrementalDatabase::default();
let file = db.add_file("\\newcommand{\\foo}{x}\n");
let other = IncrementalDatabase::default();
let other_file = other.add_file("\\newcommand{\\foo}{x}\n\nsome text.\n");
assert_eq!(
db.document_signatures(file),
other.document_signatures(other_file)
);
}
#[test]
fn parsed_bib_document_is_memoized() {
let db = IncrementalDatabase::default();
let file = db.add_file("@article{k, title = {Hi}}\n");
let _ = db.parsed_bib_tree(file);
let _ = db.parsed_bib_tree(file);
let _ = db.bib_parse_diagnostics(file);
let _ = db.bib_semantic_model(file);
assert_eq!(bib_parse_count(&db), 1);
}
#[test]
fn editing_bib_text_reparses() {
let mut db = IncrementalDatabase::default();
let file = db.add_file("@misc{a}\n");
let _ = db.parsed_bib_tree(file);
assert_eq!(bib_parse_count(&db), 1);
db.set_file_text(file, "@misc{b}\n");
let _ = db.parsed_bib_tree(file);
assert_eq!(bib_parse_count(&db), 2);
}
#[test]
fn cached_bib_tree_is_lossless() {
let db = IncrementalDatabase::default();
let input = "@article{k,\n title = {Hi},\n year = 2020,\n}\n";
let file = db.add_file(input);
assert_eq!(db.parsed_bib_tree(file).to_string(), input);
}
#[test]
fn bib_semantic_model_is_memoized() {
let db = IncrementalDatabase::default();
let file = db.add_file("@book{k, publisher = cup}\n@string{cup = {C}}\n");
let _ = db.bib_semantic_model(file);
let _ = db.bib_semantic_model(file);
assert_eq!(bib_model_count(&db), 1);
}
#[test]
fn equal_bib_edit_yields_equal_model() {
let db = IncrementalDatabase::default();
let file = db.add_file("@article{k, title = {A}}\n");
let other = IncrementalDatabase::default();
let other_file = other.add_file("@article{k, title = {A}}\n");
assert_eq!(
db.bib_semantic_model(file),
other.bib_semantic_model(other_file)
);
}
#[test]
fn clone_shares_storage() {
let db = IncrementalDatabase::default();
let file = db.add_file("\\emph{hi}\n");
let _ = db.parsed_tree(file);
assert_eq!(parse_count(&db), 1);
let clone = db.clone();
let _ = clone.parsed_tree(file);
assert_eq!(parse_count(&clone), 1);
assert_eq!(clone.file_text(file), "\\emph{hi}\n");
}