use crate::{
catalog::Catalog,
queries::ALL_QUERIES,
range::{Change, Range},
rope_provider::RopeProvider,
test_helpers::*,
workspace::apply_change_to_rope_and_tree,
*,
};
use horned_owl::{
io::{OWXParserConfiguration, ParserConfiguration, RDFParserConfiguration},
model::{AnnotatedComponent, Build},
};
use indoc::indoc;
use pos::Position;
use pretty_assertions::assert_eq;
use ropey::Rope;
use sophia::api::term::SimpleTerm;
use tempdir::TempDir;
use test_log::test;
use tower_lsp::LspService;
use tree_sitter_c2rust::Tree;
#[test]
fn parse_ontology_should_work() {
setup();
let mut parser = arrange_parser();
let source_code = "Ontology: Foobar";
let tree = parser.parse(source_code, None).unwrap();
assert_eq!(tree.root_node().has_error(), false);
}
#[test]
fn deref_all_queries_should_be_valid() {
setup();
let _ = *ALL_QUERIES;
}
#[test]
#[ignore = "Just for experimentation"]
fn reparse_ontology_node_ids() {
setup();
let mut parser = arrange_parser();
let source_code = indoc! {"
Ontology: Foobar
# The foo class
Class: Foo
Datatype: Bar
Class: FooBar
"};
let mut rope = Rope::from(source_code);
let mut tree = {
let rope_provider = RopeProvider::new(&rope);
parser
.parse_with_options(&mut |i, _| rope_provider.chunk_callback(i), None, None)
.unwrap()
};
info!("Text: \n{rope}\n");
print_tree(&tree);
info!("------ edit ------");
let edits = [
(
Range::new(Position::new(0, 10), Position::new(0, 16)),
"Foo Bar",
),
(Range::new(Position::new(3, 0), Position::new(3, 0)), "#"),
];
for (range, text) in edits {
apply_change_to_rope_and_tree(
&mut tree,
&mut rope,
&Change {
range,
text: text.to_string(),
},
)
.unwrap();
}
info!("Text: \n{rope}\n");
print_tree(&tree);
info!("------ reparsing ------");
let rope_provider = RopeProvider::new(&rope);
let tree_2 = parser
.parse_with_options(
&mut |i, _| rope_provider.chunk_callback(i),
Some(&tree),
None,
)
.unwrap();
info!("Text: \n{rope}\n");
print_tree(&tree_2);
for change in tree.changed_ranges(&tree_2) {
info!("Changed syntax at {}", Range::from(change));
}
for change in tree_2.changed_ranges(&tree) {
info!("Changed syntax (rev) at {}", Range::from(change));
}
panic!();
}
fn print_tree(tree: &Tree) {
let mut w = tree.walk();
'outer: loop {
info!(
"{}+ {}, ID: {}, C?: {}, {:?}",
" ".repeat(w.depth() as usize),
w.node().kind(),
w.node().id(),
w.node().has_changes(),
w.node().byte_range(),
);
if !w.goto_first_child() {
while !w.goto_next_sibling() {
if !w.goto_parent() {
break 'outer;
}
}
}
}
}
#[test]
#[ignore = "This was just a spike for messing around"]
fn horned_owl_should_parse_rdf_xml() {
let x = r##"
<rdf:RDF xmlns="http://www.example.com/iri#"
xml:base="http://www.example.com/iri"
xmlns:o="http://www.example.com/iri#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
<owl:Ontology rdf:about="http://www.example.com/iri">
<owl:versionIRI rdf:resource="http://www.example.com/viri"/>
</owl:Ontology>
<owl:Class rdf:about="http://www.example.com/iri#C"/>
</rdf:RDF>"##;
let mut buffer = x.as_bytes();
let b = Build::new_string();
let a =
horned_owl::io::rdf::reader::read_with_build::<String, AnnotatedComponent<String>, &[u8]>(
&mut buffer,
&b,
ParserConfiguration {
rdf: RDFParserConfiguration { lax: true },
owx: OWXParserConfiguration::default(),
},
)
.unwrap();
info!("{a:?}");
}
#[test]
#[ignore = "This was just a spike for messing around"]
fn sophia_should_parse_rdf_xml() {
use sophia::api::prelude::*;
use sophia::inmem::graph::LightGraph;
use sophia::turtle::serializer::nt::NtSerializer;
let x = r##"
<rdf:RDF xmlns="http://www.example.com/iri#"
xml:base="http://www.example.com/iri"
xmlns:o="http://www.example.com/iri#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
<owl:Ontology rdf:about="http://www.example.com/iri">
<owl:versionIRI rdf:resource="http://www.example.com/viri"/>
</owl:Ontology>
<owl:Class rdf:about="http://www.example.com/iri#C"/>
<rdf:Property rdf:about="http://www.w3.org/2000/01/rdf-schema#seeAlso">
<rdfs:isDefinedBy rdf:resource="http://www.w3.org/2000/01/rdf-schema#"/>
<rdfs:label>seeAlso</rdfs:label>
<rdfs:comment>Further information about the subject resource.</rdfs:comment>
<rdfs:range rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
<rdfs:domain rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
</rdf:Property>
</rdf:RDF>"##;
let graph: LightGraph = sophia::xml::parser::parse_str(x).collect_triples().unwrap();
let triples = graph
.triples_matching(
|s: SimpleTerm| {
s.iri()
.map(|iri| iri.as_str() == "http://www.w3.org/2000/01/rdf-schema#seeAlso")
.unwrap_or(false)
},
Any,
Any,
)
.flatten()
.flat_map(
|[subject, predicate, object]| match (subject, predicate, object) {
(SimpleTerm::Iri(r), _, c) => {
info!("Found one {c:#?}");
Some(r)
}
_ => None,
},
);
for a in triples {
info!("{a}");
}
let mut nt_stringifier = NtSerializer::new_stringifier();
let example2 = nt_stringifier.serialize_graph(&graph).unwrap().as_str();
info!("nt={example2:#?}");
}
#[test(tokio::test)]
async fn backend_did_open_should_create_document() {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let url = Url::from_file_path(dir.path().join("foo.omn")).unwrap();
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: "abc".to_string(),
},
})
.await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
let docs = workspace.internal_documents().collect_vec();
let doc = docs
.iter()
.exactly_one()
.unwrap_or_else(|_| panic!("Should be exactly one"));
assert_eq!(doc.uri(), &url);
assert_eq!(doc.version(), 0);
assert_eq!(doc.rope().to_string(), "abc");
assert!(doc.tree().root_node().is_error());
}
#[test(tokio::test)]
async fn backend_did_change_should_update_internal_rope() -> error::Result<()> {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: "DE😊F".to_string(),
},
})
.await;
{
let sync = service.inner().read_sync().await;
info!("{sync:#?}");
}
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 2,
},
content_changes: vec![
TextDocumentContentChangeEvent {
range: Some(lsp_types::Range {
start: lsp_types::Position::new(0, 0),
end: lsp_types::Position::new(0, 0),
}),
range_length: None,
text: "A😊BC".to_string(),
},
TextDocumentContentChangeEvent {
range: Some(lsp_types::Range {
start: lsp_types::Position::new(
0,
14,
),
end: lsp_types::Position::new(0, 14),
}),
range_length: None,
text: "GH😊I".to_string(),
},
],
})
.await;
let sync = service.inner().read_sync().await;
info!("{sync:#?}");
let (doc, _) = sync.get_internal_document(&ontology_url).unwrap();
let doc_content = doc.rope().to_string();
assert_eq!(doc_content, "A😊BCDE😊FGH😊I");
Ok(())
}
#[test(tokio::test)]
async fn backend_hover_on_class_should_show_class_info() {
setup();
let service = arrange_backend(
None,
vec![("http://www.w3.org/2000/01/rdf-schema#", "dummy")],
)
.await;
let dir = TempDir::new("owl-ms-test").unwrap();
let url = Url::from_file_path(dir.path().join("file.omn")).unwrap();
let ontology = r#"
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: HoverOnto
Class: Janek
Annotations:
rdfs:label "Janek der Coder"@de
AnnotationProperty: rdfs:label
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let hover_result = service
.inner()
.hover(HoverParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(3, 21),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
})
.await
.unwrap();
assert_empty_diagnostics(&service).await;
let hover_result = hover_result.unwrap();
let range = hover_result.range.unwrap();
assert_eq!(
range,
lsp_types::Range {
start: lsp_types::Position::new(3, 19),
end: lsp_types::Position::new(3, 24)
}
);
let contents = match hover_result.contents {
HoverContents::Scalar(MarkedString::String(str)) => str,
_ => panic!("Did not think of that"),
};
assert!(contents.contains("Class"));
assert!(contents.contains("**Janek der Coder**"));
assert!(contents.contains("`label`: Janek der Coder"));
assert!(contents.contains("IRI: Janek"));
}
async fn arrange_multi_file_ontology() -> (LspService<Backend>, TempDir) {
let tmp_dir = arrange_workspace_folders(|dir| {
vec![
WorkspaceMember::Folder {
name: "ontology-a".into(),
children: vec![
WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://ontology-a.org/a1.omn", "a1.omn")
.with_uri("http://ontology-a.org/a2.omn", "a2.omn"),
),
WorkspaceMember::OmnFile {
name: "a2.omn".into(),
content: r#"
Prefix: #comment
: #comment
<http://ontology-a.org/ontology#> #comment
Prefix: #comment
rdfs: #comment
<http://www.w3.org/2000/01/rdf-schema#> #comment
Ontology: #comment
<http://ontology-a.org/a2.omn> #comment
AnnotationProperty: rdfs:label
Class: #comment
ClassA2 #comment
Annotations: #comment
rdfs:label #comment
"Some class in A2", #comment
test #comment
"test" #comment
Class: test
"#
.into(),
},
],
},
WorkspaceMember::Folder {
name: "ontology-b".into(),
children: vec![
WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://ontology-b.org/b1.omn", "b1.omn")
.with_uri("http://ontology-b.org/b2.omn", "b2.omn"),
),
WorkspaceMember::OmnFile {
name: "b2.omn".into(),
content: r#"
Prefix: : <http://ontology-a.org/ontology#>
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: <http://ontology-b.org/b2.omn>
AnnotationProperty: rdfs:label
Class: ClassB2
Annotations:
rdfs:label "Some class in B2"
"#
.into(),
},
],
},
]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test wosrkpace".into(),
}),
vec![
("http://www.w3.org/2000/01/rdf-schema#", ""), ],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let ontology = r#"
Prefix: : <http://ontology-a.org/ontology#>
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: <http://ontology-a.org/a1.omn>
Import: <http://ontology-a.org/a2.omn>
AnnotationProperty: rdfs:label
Class: ClassA1
Annotations:
rdfs:label "Some class in A1"
SubClassOf: ClassA2,ClassB2
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
(service, tmp_dir)
}
#[test(tokio::test)]
async fn backend_hover_in_multi_file_ontology_should_work() {
setup();
let (service, tmp_dir) = arrange_multi_file_ontology().await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let hover_result = service
.inner()
.hover(HoverParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(9, 31),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
})
.await
.unwrap();
let diagnostics = service_diagnostics(&service).await;
assert!(
diagnostics.len() == 1,
"The not defined class B2 should generate a diagnostic"
);
let hover_result = hover_result.unwrap();
let range = hover_result.range.unwrap();
assert_eq!(
range,
lsp_types::Range {
start: lsp_types::Position::new(9, 28),
end: lsp_types::Position::new(9, 35)
}
);
let contents = match hover_result.contents {
HoverContents::Scalar(MarkedString::String(str)) => str,
_ => panic!("Did not think of that"),
};
let sync = service.inner().read_sync().await;
info!("{:#?}", sync.workspaces());
info!("{contents}");
assert!(contents.contains("Some class in A2"));
assert!(contents.contains("test"));
}
#[test(tokio::test)]
async fn backend_hover_in_multi_file_ontology_on_not_imported_iri_should_not_work() {
setup();
let (service, tmp_dir) = arrange_multi_file_ontology().await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let hover_result = service
.inner()
.hover(HoverParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(9, 38),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
})
.await
.unwrap();
let diagnostics = service_diagnostics(&service).await;
assert!(
diagnostics.len() == 1,
"The not defined class B2 should generate a diagnostic"
);
let hover_result = hover_result.unwrap();
let contents = match hover_result.contents {
HoverContents::Scalar(MarkedString::String(str)) => str,
_ => panic!("Did not think of that"),
};
assert!(contents.contains("ClassB2"));
assert!(!contents.contains("Some class"));
assert!(!contents.contains("b2"));
}
#[test(tokio::test)]
async fn backend_hover_on_external_simple_iri_should_show_external_info() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::Folder {
name: "ontology-a".into(),
children: vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://ontology-a.org/a1", "a1.omn")
.with_uri("http://ontology-a.org/a2", "http://ontology-a.org/a2.owx"),
)],
}]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test wosrkpace".into(),
}),
vec![
(
"http://ontology-a.org/a2.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://foo.org/ontology"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
ontologyIRI="http://foo.org/ontology#">
<Prefix name="" IRI="http://foo.org/ontology#"/>
<Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>
<Declaration>
<Class IRI="ClassA2"/>
</Declaration>
<AnnotationAssertion>
<AnnotationProperty abbreviatedIRI="rdfs:label"/>
<IRI>ClassA2</IRI>
<Literal>Some class in A2</Literal>
</AnnotationAssertion>
</Ontology>
"##,
),
("http://www.w3.org/2000/01/rdf-schema#", "dummy"),
],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let ontology = r#"
Prefix: : <http://foo.org/ontology#>
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: <http://ontology-a.org/a1>
Import: <http://ontology-a.org/a2>
Class: ClassA1
Annotations:
rdfs:label "Some class in A1"
SubClassOf: ClassA2,ClassB2
AnnotationProperty: rdfs:label
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let hover_result = service
.inner()
.hover(HoverParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(8, 32), },
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
})
.await
.unwrap();
let diagnostics = service_diagnostics(&service).await;
debug!("{diagnostics:#?}");
assert!(
diagnostics.len() == 1,
"The not defined class B2 should generate a diagnostic"
);
let hover_result = hover_result.unwrap();
let contents = match hover_result.contents {
HoverContents::Scalar(MarkedString::String(str)) => str,
_ => panic!("Did not think of that"),
};
info!("contents={contents}");
assert!(!contents.contains("**ClassA2**"));
assert!(contents.contains("Some class in A2"));
}
#[test(tokio::test)]
async fn backend_hover_on_external_full_iri_should_show_external_info() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::Folder {
name: "ontology-a".into(),
children: vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://ontology-a.org/a1", "a1.omn")
.with_uri("http://ontology-a.org/a2", "http://ontology-a.org/a2.owx"),
)],
}]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test wosrkpace".into(),
}),
vec![
(
"http://ontology-a.org/a2.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://foo.org/a"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
ontologyIRI="http://ontology-a.org/a2.owx">
<Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>
<Declaration>
<Class IRI="#ClassA2"/>
</Declaration>
<AnnotationAssertion>
<AnnotationProperty abbreviatedIRI="rdfs:label"/>
<IRI>#ClassA2</IRI>
<Literal>Some class in A2</Literal>
</AnnotationAssertion>
</Ontology>
"##,
),
("http://www.w3.org/2000/01/rdf-schema#", "dummy"),
],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let ontology = r#"
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: <http://ontology-a.org/a1>
Import: <http://ontology-a.org/a2>
Class: ClassA1
Annotations:
rdfs:label "Some class in A1"
SubClassOf: <http://ontology-a.org/a2.owx#ClassA2>,ClassB2
AnnotationProperty: rdfs:label
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let hover_result = service
.inner()
.hover(HoverParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(7, 32),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
})
.await
.unwrap();
let diagnostics = service_diagnostics(&service).await;
debug!("{diagnostics:#?}");
assert!(
diagnostics.len() == 1,
"The not defined class B2 should generate a diagnostic"
);
let hover_result = hover_result.unwrap();
let contents = match hover_result.contents {
HoverContents::Scalar(MarkedString::String(str)) => str,
_ => panic!("Did not think of that"),
};
info!("contents={contents}");
assert!(!contents.contains("**ClassA2**"));
assert!(contents.contains("Some class in A2"));
assert!(contents.contains("IRI: http://ontology-a.org/a2.owx#ClassA2"));
}
#[test(tokio::test)]
async fn backend_hover_on_external_rdf_document_at_simple_iri_should_show_external_info() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::Folder {
name: "ontology-a".into(),
children: vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://ontology-a.org/a1", "a1.omn")
.with_uri("http://ontology-a.org/a2", "http://ontology-a.org/a2.rdf"),
)],
}]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test wosrkpace".into(),
}),
vec![
(
"http://ontology-a.org/a2.rdf",
r##"
<?xml version="1.0"?>
<rdf:RDF xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://www.w3.org/2002/07/owl"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
<Ontology rdf:about="http://ontology-a.org/a2.rdf"/>
<!-- http://ontology-a.org/a2#ClassA2 -->
<Class rdf:about="http://foo.org/ontology#ClassA2">
<rdfs:label>Some class in A2</rdfs:label>
</Class>
</rdf:RDF>
"##,
),
("http://foo.org/ontology#", "dummy"),
("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "dummy"),
("http://www.w3.org/2002/07/owl#", "dummy"),
("http://www.w3.org/2000/01/rdf-schema#", "dummy"),
],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let ontology = r#"
Prefix: : <http://foo.org/ontology#>
Ontology: <http://ontology-a.org/a1>
Import: <http://ontology-a.org/a2>
Class: ClassA1
Annotations:
rdfs:label "Some class in A1"
SubClassOf: ClassA2,ClassB2
AnnotationProperty: rdfs:label
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let hover_result = service
.inner()
.hover(HoverParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(7, 32),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
})
.await
.unwrap();
let diagnostics = service_diagnostics(&service).await;
debug!("{diagnostics:#?}");
assert!(
diagnostics.len() == 1,
"The not defined class B2 should generate a diagnostic"
);
let hover_result = hover_result.unwrap();
let contents = match hover_result.contents {
HoverContents::Scalar(MarkedString::String(str)) => str,
_ => panic!("Did not think of that"),
};
info!("contents={contents}");
assert!(!contents.contains("**ClassA2**"));
assert!(contents.contains("Some class in A2"));
}
#[test(tokio::test)]
async fn backend_formatting_on_file_should_correctly_format() -> error::Result<()> {
setup();
let source = indoc! {"
Prefix: a: <http://a.b/c/>
Prefix: b: <http://a.b/b/>
Ontology: foo ver
Class: A SubClassOf: Y Annotations: rdfs:label \"Y\" EquivalentTo: Y
DisjointWith: Y DisjointUnionOf: Y,Z HasKey: Y
Datatype: B
EquivalentTo: Y
DataProperty: C
ObjectProperty: D
AnnotationProperty: E
Individual: F
Class: C
SubClassOf: p some (A and B)
SubClassOf: inverse p some (A and B)
SubClassOf: inverse p some A and B
Class: Y
Class: Z
AnnotationProperty: rdfs:label
Class: p
"};
let target = indoc! {"
Prefix: a: <http://a.b/c/>
Prefix: b: <http://a.b/b/>
Ontology: foo ver
Class: A
SubClassOf: Y
Annotations: rdfs:label \"Y\"
EquivalentTo: Y
DisjointWith: Y
DisjointUnionOf: Y, Z
HasKey: Y
Datatype: B
EquivalentTo: Y
DataProperty: C
ObjectProperty: D
AnnotationProperty: E
Individual: F
Class: C
SubClassOf: p some ( A and B )
SubClassOf: inverse p some ( A and B )
SubClassOf: inverse p some A and B
Class: Y
Class: Z
AnnotationProperty: rdfs:label
Class: p
"};
let tmp_dir = arrange_workspace_folders(|_| vec![]);
let service = arrange_backend(
None,
vec![("http://a.b/c/", "dummy"), ("http://a.b/b/", "dummy")],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl-ms".to_string(),
version: 0,
text: source.to_string(),
},
})
.await;
let result = service
.inner()
.formatting(DocumentFormattingParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
options: FormattingOptions {
tab_size: 4,
insert_spaces: true,
..FormattingOptions::default()
},
})
.await
.unwrap();
assert_empty_diagnostics(&service).await;
let edits = result.unwrap();
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: url.clone(),
version: 1,
},
content_changes: edits
.iter()
.sorted_by_key(|e| e.range.start)
.rev()
.map(|e| TextDocumentContentChangeEvent {
range: Some(e.range),
range_length: None,
text: e.new_text.clone(),
})
.collect(),
})
.await;
let sync = service.inner().read_sync().await;
let workspace = sync.get_workspace(&url).unwrap();
let doc = workspace
.get_internal_document(&url.to_file_path().unwrap())
.unwrap();
assert_empty_diagnostics(&service).await;
assert_eq!(doc.rope().to_string(), target);
Ok(())
}
#[test(tokio::test)]
async fn backend_inlay_hint_on_external_simple_iri_should_show_iri() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::Folder {
name: "ontology-a".into(),
children: vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://ontology-a.org/a1", "a1.omn")
.with_uri("http://ontology-a.org/a2", "http://ontology-a.org/a2.owx"),
)],
}]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test wosrkpace".into(),
}),
vec![
(
"http://ontology-a.org/a2.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://foo.org/a"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
ontologyIRI="http://foo.org/ontology#">
<Prefix name="" IRI="http://foo.org/ontology#"/>
<Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>
<Declaration>
<Class IRI="ClassA2"/>
</Declaration>
<AnnotationAssertion>
<AnnotationProperty abbreviatedIRI="rdfs:label"/>
<IRI>ClassA2</IRI>
<Literal>Some class in A2</Literal>
</AnnotationAssertion>
</Ontology>
"##,
),
("http://www.w3.org/2000/01/rdf-schema#", ""), ],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let ontology = r#"
Prefix: : <http://foo.org/ontology#>
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: <http://ontology-a.org/a1>
Import: <http://ontology-a.org/a2>
Class: ClassA1
Annotations:
rdfs:label "Some class in A1"
SubClassOf: ClassA2,ClassB2
AnnotationProperty: rdfs:label
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let result = service
.inner()
.inlay_hint(InlayHintParams {
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
text_document: TextDocumentIdentifier { uri: url.clone() },
range: lsp_types::Range {
start: lsp_types::Position::new(0, 0),
end: lsp_types::Position::new(999, 0),
},
})
.await
.unwrap();
let diagnostics = service_diagnostics(&service).await;
debug!("{diagnostics:#?}");
assert!(
diagnostics.len() == 1,
"The not defined class B2 should generate a diagnostic"
);
let result = result.unwrap();
info!("result={result:#?}");
assert_eq!(result.len(), 2);
assert!(result.iter().any(|x| match &x.label {
InlayHintLabel::String(a) => a.contains("Some class in A1"),
InlayHintLabel::LabelParts(_) => unreachable!(),
}));
assert!(result.iter().any(|x| match &x.label {
InlayHintLabel::String(a) => {
a.contains("Some class in A2")
}
InlayHintLabel::LabelParts(_) => unreachable!(),
}));
}
#[test(tokio::test)]
async fn backend_import_resolve_should_load_documents() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![
WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://external.org/shared.omn", "foobaronto.omn")
.with_uri("http://foobar.org/ontology/", "foo.omn"),
),
WorkspaceMember::OmnFile {
name: "foobaronto.omn".into(),
content: "".into(),
},
]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![],
)
.await;
let file_url = Url::from_file_path(tmp_dir.path().join("foo.omn")).expect("valid url");
let ontology = r#"
Ontology: <http://foobar.org/ontology/>
Import: <http://external.org/shared.omn>
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: file_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
assert_empty_diagnostics(&service).await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
assert_eq!(workspaces.len(), 1, "all files should be in one workspace");
let workspace = workspaces.first().unwrap();
info!(" Workspace documents {:#?}", workspace.internal_documents());
let document_count = workspace.internal_documents().count();
assert_eq!(document_count, 2);
}
#[test(tokio::test)]
async fn backend_did_change_should_remove_old_infos() {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();
let ontology = r#"Ontology: <http://a.b/multi-file>
Class: class-in-first-file
Annotations: rdfs:label "This class is in the first file"
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 1,
},
content_changes: vec![TextDocumentContentChangeEvent {
text: "".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(2, 0),
end: lsp_types::Position::new(2, 61),
}),
range_length: None,
}],
})
.await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
let document = workspace
.internal_documents()
.exactly_one()
.unwrap_or_else(|_| panic!("Multiple documents"));
assert_eq!(
document.rope().to_string(),
r#"Ontology: <http://a.b/multi-file>
Class: class-in-first-file
"#
);
}
#[test(tokio::test)]
async fn backend_workspace_symbols_should_work() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![
WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog.xml").to_str().unwrap())
.with_uri("http://foo.org/a.omn", "a.omn"),
),
WorkspaceMember::OmnFile {
name: "a.omn".into(),
content: r#"
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: <http://foo.org/a>
AnnotationProperty: rdfs:label
Class: some-class # This will get found by iri
Annotations:
rdfs:label "Some class"
Class: C_123 # This will get found by label
Annotations:
rdfs:label "some number class"
Class: some-iri-class # This will get found by iri and has no label
"#
.into(),
},
WorkspaceMember::OmnFile {
name: "b.omn".into(),
content: r#"
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: <http://foo.org/b>
AnnotationProperty: rdfs:label
Import: <http://foo.org/a.omn>
Class: some-other-class # This will also be found by iri
Annotations:
rdfs:label "Some other class"
"#
.into(),
},
]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "foo".into(),
}),
vec![],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("c.omn")).unwrap();
let ontology = r#"
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: <http://foo.org/c>
Import: <http://foo.org/a.omn>
Class: some-other-class-at-c
Annotations:
rdfs:label "Some other class at c"
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let result = service
.inner()
.symbol(WorkspaceSymbolParams {
partial_result_params: PartialResultParams {
partial_result_token: None,
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
query: "some".to_string(),
})
.await;
assert_empty_diagnostics(&service).await;
let symbols = result
.expect("Symbols should not throw errors")
.expect("Symbols should contain something");
info!("{symbols:#?}");
assert_eq!(symbols.len(), 4);
assert!(symbols.iter().any(|s| s.name == "Some class"));
assert!(!symbols.iter().any(|s| s.name == "some-class"));
assert!(symbols.iter().any(|s| s.name == "Some other class at c"));
assert!(!symbols.iter().any(|s| s.name == "some-other-class-at-c"));
assert!(symbols.iter().any(|s| s.name == "some number class"));
assert!(!symbols.iter().any(|s| s.name == "C_123"));
assert!(symbols.iter().any(|s| s.name == "some-iri-class"));
}
#[test(tokio::test)]
async fn backend_did_open_should_load_external_documents_via_http() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![
WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://foo.org/a.omn", "http://foo.org/version/file.owx")
.with_uri("http://foo.org/c.omn", "c.omn"),
),
WorkspaceMember::OmnFile {
name: "a.omn".into(),
content: r#"
Ontology: <http://foo.org/a>
AnnotationProperty: rdfs:label
Class: some-class
Annotations:
rdfs:label "Some class"
"#
.into(),
},
]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "foo".into(),
}),
vec![(
"http://foo.org/version/file.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://foo.org/a"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
ontologyIRI="http://foo.org/a">
<Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>
<Declaration>
<Class IRI="#SomeOtherClass"/>
</Declaration>
<AnnotationAssertion>
<AnnotationProperty abbreviatedIRI="rdfs:label"/>
<IRI>#SomeOtherClass</IRI>
<Literal>Some other class at a</Literal>
</AnnotationAssertion>
</Ontology>
"##,
)],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("c.omn")).unwrap();
let ontology = r#"
Ontology: <http://foo.org/c>
Import: <http://foo.org/a.omn>
AnnotationProperty: rdfs:label
Class: some-other-class-at-c
Annotations:
rdfs:label "Some other class at c"
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
assert_empty_diagnostics(&service).await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces
.iter()
.exactly_one()
.expect("Only one workspace should be crated");
assert_eq!(
workspace.internal_documents().len(),
1,
"One internal document should be loaded. It was given"
);
assert_eq!(
workspace.external_documents().len(),
1,
"One external document should be loaded"
);
}
#[test(tokio::test)]
async fn backend_did_open_should_load_external_rdf_via_http() {
setup();
let tmp_dir = arrange_workspace_folders(|_| vec![]);
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "foo".into(),
}),
vec![
(
"http://www.w3.org/2000/01/rdf-schema#",
r##"<?xml version="1.0"?>
<rdf:RDF xmlns="http://www.example.com/iri#"
xml:base="http://www.example.com/iri"
xmlns:o="http://www.example.com/iri#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
<owl:Ontology rdf:about="http://www.example.com/iri">
<owl:versionIRI rdf:resource="http://www.example.com/viri"/>
</owl:Ontology>
<owl:Class rdf:about="http://www.example.com/iri#C"/>
</rdf:RDF>"##,
),
("http://www.example.com/", "dummy"),
],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("has-rdf.omn")).unwrap();
let ontology = r"
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: <http://foo.org/has-rdf>
AnnotationProperty: rdfs:label
Class: some-class
Annotations:
rdfs:seeAlso some-other-class
Class: some-other-class
AnnotationProperty: rdfs:label
AnnotationProperty: rdfs:seeAlso
";
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
assert_empty_diagnostics(&service).await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces
.iter()
.exactly_one()
.expect("Only one workspace should be crated");
assert_eq!(
workspace.internal_documents().len(),
1,
"One internal document should be loaded. It was given"
);
info!("External Documents: {:#?}", workspace.external_documents());
assert_eq!(
workspace.external_documents().len(),
1,
"One external document should be loaded"
);
}
#[test(tokio::test)]
async fn backend_hover_should_use_external_rdf_info() {
setup();
let tmp_dir = arrange_workspace_folders(|_| vec![]);
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "foo".into(),
}),
vec![
(
"http://www.w3.org/2000/01/rdf-schema#",
r##"
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:owl="http://www.w3.org/2002/07/owl#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<owl:Ontology
rdf:about="http://www.w3.org/2000/01/rdf-schema#"
dc:title="The RDF Schema vocabulary (RDFS)"/>
<rdfs:Class rdf:about="http://www.w3.org/2000/01/rdf-schema#Resource">
<rdfs:isDefinedBy rdf:resource="http://www.w3.org/2000/01/rdf-schema#"/>
<rdfs:label>Resource</rdfs:label>
<rdfs:comment>The class resource, everything.</rdfs:comment>
</rdfs:Class>
<rdf:Property rdf:about="http://www.w3.org/2000/01/rdf-schema#seeAlso">
<rdfs:isDefinedBy rdf:resource="http://www.w3.org/2000/01/rdf-schema#"/>
<rdfs:label>Seeeeee Alsooooooo</rdfs:label>
<rdfs:comment>Further information about the subject resource.</rdfs:comment>
<rdfs:range rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
<rdfs:domain rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
</rdf:Property>
</rdf:RDF>
"##,
),
("http://purl.org/dc/elements/1.1/", "dummy"),
("http://purl.org/dc/elements/1.1", "dummy"),
("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "dummy"),
("http://www.w3.org/2002/07/owl#", "dummy"),
],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("has-rdf.omn")).unwrap();
let ontology = indoc! {r#"
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Ontology: <http://foo.org/has-rdf>
Class: some-class
Annotations:
rdfs:seeAlso "bar"
Class: some-other-class
AnnotationProperty: rdfs:seeAlso
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let result = service
.inner()
.hover(HoverParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url },
position: tower_lsp::lsp_types::Position {
line: 2,
character: 13,
},
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
})
.await;
assert_empty_diagnostics(&service).await;
let result = result.unwrap();
let result = result.unwrap();
let contents = result.contents;
let string = match contents {
HoverContents::Scalar(MarkedString::String(string)) => string,
_ => todo!(),
};
info!("string={string}");
assert!(string.contains("Seeeeee Alsooooooo"));
}
#[test(tokio::test)]
async fn backend_did_open_should_load_external_documents_via_file() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![
WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://foo.org/a", "a.owx")
.with_uri("http://foo.org/c", "c.omn"),
),
WorkspaceMember::OwxFile {
name: "a.owx".into(),
content: r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://foo.org/a"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
ontologyIRI="http://foo.org/a">
<Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>
<Declaration>
<Class IRI="#SomeOtherClass"/>
</Declaration>
<AnnotationAssertion>
<AnnotationProperty abbreviatedIRI="rdfs:label"/>
<IRI>#SomeOtherClass</IRI>
<Literal>Some other class at a</Literal>
</AnnotationAssertion>
</Ontology>
"##
.into(),
},
]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "foo".into(),
}),
vec![],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("c.omn")).unwrap();
let ontology = r#"
Ontology: <http://foo.org/c>
Import: <http://foo.org/a>
AnnotationProperty: rdfs:label
Class: some-other-class-at-c
Annotations:
rdfs:label "Some other class at c"
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
assert_empty_diagnostics(&service).await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces
.iter()
.exactly_one()
.expect("Only one workspace should be crated");
assert_eq!(
workspace.internal_documents().len(),
1,
"One internal document should be loaded. It was given"
);
assert_eq!(
workspace.external_documents().len(),
1,
"One external document should be loaded"
);
}
#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_class() {
setup();
let ontology = indoc! {r#"
Ontology: <http://foo.org/a>
<PARTIAL>
Class: c
"#};
backend_completion_test_helper("Cl", "Class:", ontology).await;
}
#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_class_at_the_end() {
setup();
let ontology = indoc! {r#"
Ontology: <http://foo.org/a> Version
<PARTIAL>
"#};
backend_completion_test_helper("Cl", "Class:", ontology).await;
}
#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_datatype() {
setup();
let ontology = indoc! {r#"
Ontology: <http://foo.org/a>
<PARTIAL>
Class: c
"#};
backend_completion_test_helper("Datat", "Datatype:", ontology).await;
}
#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_integer() {
setup();
let ontology = indoc! {r#"
Ontology: <http://foo.org/a>
Datatype: Foo
EquivalentTo: (<PARTIAL>
Class: c
"#};
backend_completion_test_helper("in", "integer", ontology).await;
}
#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_some() {
setup();
let ontology = indoc! {r#"
Ontology: <http://foo.org/a>
Ontology:
Class: Person
SubClassOf: hasAge <PARTIAL> 1 and hasAge only not NegInt
Class: c
"#};
backend_completion_test_helper("so", "some", ontology).await;
}
#[test(tokio::test)]
async fn backend_completion_should_work_for_keyword_functionnal() {
setup();
let ontology = indoc! {r#"
Ontology: <http://foo.org/a>
Ontology:
ObjectProperty: Thing
Characteristics: <PARTIAL>
Class: c
"#};
backend_completion_test_helper("Fu", "Functional", ontology).await;
}
async fn backend_completion_test_helper(partial: &str, full: &str, ontology: &str) -> Vec<String> {
let tmp_dir = arrange_workspace_folders(|_| vec![]);
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "foo".into(),
}),
vec![],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("a.omn")).unwrap();
let rope = Rope::from_str(ontology);
let pos = ontology
.lines()
.enumerate()
.find_map(|(li, line)| {
line.find("<PARTIAL>")
.map(|ci| Position::new(li as u32, ci as u32))
})
.expect("Should contain <PARTIAL> str");
let ontology = ontology.replace("<PARTIAL>", partial);
let pos = pos.moved_right(partial.len() as u32, &rope);
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let pos = pos.into_lsp(&rope, &PositionEncodingKind::UTF16).unwrap();
let result = service
.inner()
.completion(CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url },
position: pos,
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
context: None,
})
.await;
let result = result.expect("Result shoud not produce error");
let result = result.expect("Result should contain completion response");
match result {
CompletionResponse::Array(completion_items) => {
let completion_items = completion_items
.into_iter()
.filter(|i| i.kind == Some(CompletionItemKind::KEYWORD))
.collect_vec();
assert_eq!(completion_items.len(), 1);
assert_eq!(completion_items[0].label, full);
completion_items.into_iter().map(|i| i.label).collect_vec()
}
CompletionResponse::List(_) => unimplemented!(),
}
}
#[test(tokio::test)]
async fn backend_completion_should_not_panic() {
setup();
let tmp_dir = arrange_workspace_folders(|_| vec![]);
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "foo".into(),
}),
vec![],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("a.omn")).unwrap();
let ontology = indoc! {r#"
Ontology: <http://foo.org/a>
ööööö
Class: some-other-class-at-c
Annotations:
rdfs:label "Some other class at c"
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let _ = service
.inner()
.completion(CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url },
position: lsp_types::Position::new(2, 6),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
context: None,
})
.await;
}
#[test(tokio::test)]
async fn backend_completion_on_empty_doc_should_suggest_ontology() {
setup();
let tmp_dir = arrange_workspace_folders(|_| vec![]);
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test-doc".into(),
}),
vec![],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("test-doc.omn")).unwrap();
let ontology = "";
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let completions = service
.inner()
.completion(CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url },
position: lsp_types::Position::new(0, 0),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
context: None,
})
.await;
let completions = completions.unwrap().unwrap();
let items = match completions {
CompletionResponse::Array(completion_items) => completion_items,
CompletionResponse::List(_) => todo!(),
};
let labels = items.iter().map(|i| &i.label).collect_vec();
assert!(labels.contains(&&"Ontology:".to_string()));
assert!(labels.contains(&&"Prefix:".to_string()));
}
#[test(tokio::test)]
async fn backend_completion_with_iri_should_complete_to_iri() {
setup();
let tmp_dir = arrange_workspace_folders(|_| vec![]);
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "foo".into(),
}),
vec![],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("a.omn")).unwrap();
let ontology = indoc! {r#"
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Prefix: : <http://foo.org/a#>
Ontology: <http://foo.org/a>
Class: F_1234
Annotations:
rdfs:label "Some Class"
Class: Other
SubClassOf: Som #<--here
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let res = service
.inner()
.completion(CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url },
position: lsp_types::Position::new(7, 19),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
context: None,
})
.await;
let res = res.unwrap();
let res = res.unwrap();
let items = match res {
CompletionResponse::Array(completion_items) => completion_items,
CompletionResponse::List(_) => unimplemented!(),
};
let item = items.into_iter().exactly_one().unwrap();
assert_eq!(item.label, "Some Class");
assert_eq!(item.insert_text, Some("F_1234".to_string()));
}
#[test(tokio::test)]
async fn backend_completion_with_iri_should_be_case_insensitive() {
setup();
let tmp_dir = arrange_workspace_folders(|_| vec![]);
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "foo".into(),
}),
vec![],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("a.omn")).unwrap();
let ontology = indoc! {r#"
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Prefix: : <http://foo.org/a#>
Ontology: <http://foo.org/a>
Class: F_1234
Annotations:
rdfs:label "Some Class"
Class: Other
SubClassOf: som #<--here (lowercase)
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let res = service
.inner()
.completion(CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url },
position: lsp_types::Position::new(7, 19),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
context: None,
})
.await;
let res = res.unwrap();
let res = res.unwrap();
let items = match res {
CompletionResponse::Array(completion_items) => completion_items,
CompletionResponse::List(_) => unimplemented!(),
};
let item = items.into_iter().exactly_one().unwrap();
assert_eq!(item.label, "Some Class");
assert_eq!(item.insert_text, Some("F_1234".to_string()));
}
#[test(tokio::test)]
async fn backend_references_in_multi_file_ontology_should_work() {
setup();
let (service, tmp_dir) = arrange_multi_file_ontology().await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let result = service
.inner()
.references(ReferenceParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(9, 31),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
context: ReferenceContext {
include_declaration: true,
},
})
.await
.unwrap();
let diagnostics = service_diagnostics(&service).await;
assert!(
diagnostics.len() == 1,
"The not defined class B2 should generate a diagnostic"
);
let url2 = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a2.omn")).unwrap();
let result = result.unwrap();
info!("{result:#?}");
assert_eq!(result.len(), 2);
assert!(result.iter().any(|l| l.uri == url));
assert!(result.iter().any(|l| l.uri == url2));
}
#[test(tokio::test)]
async fn backend_references_without_def_should_not_show_def() {
setup();
let (service, tmp_dir) = arrange_multi_file_ontology().await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let result = service
.inner()
.references(ReferenceParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(9, 31), },
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
context: ReferenceContext {
include_declaration: false,
},
})
.await
.unwrap();
let diagnostics = service_diagnostics(&service).await;
assert!(
diagnostics.len() == 1,
"The not defined class B2 should generate a diagnostic"
);
let result = result.unwrap();
info!("{result:#?}");
assert_eq!(result.len(), 1);
assert!(result.iter().any(|l| l.uri == url));
}
#[test(tokio::test)]
async fn backend_goto_definition_in_multi_file_ontology_should_work() {
setup();
let (service, tmp_dir) = arrange_multi_file_ontology().await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let result = service
.inner()
.goto_definition(GotoDefinitionParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(9, 31),
},
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
})
.await
.unwrap();
let diagnostics = service_diagnostics(&service).await;
assert!(
diagnostics.len() == 1,
"The not defined class B2 should generate a diagnostic"
);
let url2 = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a2.omn")).unwrap();
let result = result.unwrap();
match result {
GotoDefinitionResponse::Array(locations) => {
let location = locations.into_iter().exactly_one().unwrap();
assert_eq!(location.uri, url2);
}
_ => todo!(),
}
}
#[test(tokio::test)]
async fn backend_document_symbols_in_multi_file_ontology_should_just_show_symbols_from_active_file()
{
setup();
let (service, tmp_dir) = arrange_multi_file_ontology().await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a2.omn")).unwrap();
{
let sync = service.inner().read_sync().await;
debug!("Sync Backend: \n{sync:#?}");
}
let result = service
.inner()
.document_symbol(DocumentSymbolParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
})
.await
.unwrap();
let diagnostics = service_diagnostics(&service).await;
assert!(
diagnostics.len() == 1,
"The not defined class B2 should generate a diagnostic"
);
match result.unwrap() {
DocumentSymbolResponse::Flat(symbol_informations) => {
let info = symbol_informations
.iter()
.filter(|si| si.name != "rdfs:label")
.filter(|si| si.name != "test")
.exactly_one()
.unwrap();
assert_eq!(info.name, "Some class in A2"); }
DocumentSymbolResponse::Nested(_document_symbols) => todo!(),
}
}
#[test(tokio::test)]
async fn backend_rename_simple_iri_should_work() {
setup();
let ontology = indoc! {"
Prefix: : <https://example.com/ontology#>
Prefix: o: <https://example.com/ontology#>
Ontology:
Class: B
SubClassOf: o:B, B, <https://example.com/ontology#B>
"};
let new_ontology = indoc! {"
Prefix: : <https://example.com/ontology#>
Prefix: o: <https://example.com/ontology#>
Ontology:
Class: beta
SubClassOf: beta, beta, beta
"};
backend_rename_helper(ontology, new_ontology, Position::new(4, 7), "beta").await;
}
#[test(tokio::test)]
async fn backend_rename_at_end_should_work() {
setup();
let ontology = indoc! {"
Prefix: : <https://example.com/ontology#>
Ontology:
Class: B
"};
let new_ontology = indoc! {"
Prefix: : <https://example.com/ontology#>
Ontology:
Class: beta
"};
backend_rename_helper(ontology, new_ontology, Position::new(3, 8), "beta").await;
}
#[test(tokio::test)]
async fn backend_rename_prefixless_simple_iri_should_work() {
setup();
let ontology = indoc! {"
Ontology:
Class: B
SubClassOf: B
"};
let new_ontology = indoc! {"
Ontology:
Class: beta
SubClassOf: beta
"};
backend_rename_helper(ontology, new_ontology, Position::new(1, 7), "beta").await;
}
#[test(tokio::test)]
async fn backend_rename_unknown_abbriviated_iri_should_work() {
setup();
let ontology = indoc! {"
Ontology:
Class: unknown:B
SubClassOf: unknown:B
"};
let new_ontology = indoc! {"
Ontology:
Class: unknown:beta
SubClassOf: unknown:beta
"};
backend_rename_helper(ontology, new_ontology, Position::new(1, 7), "beta").await;
}
#[test(tokio::test)]
async fn backend_rename_full_iri_should_shorten() {
setup();
let ontology = indoc! {"
Prefix: : <https://example.com/ontology#>
Prefix: o: <https://example.com/ontology#>
Ontology:
Class: <https://example.com/ontology#B>
SubClassOf: o:B, B, <https://example.com/ontology#B>
"};
let new_ontology = indoc! {"
Prefix: : <https://example.com/ontology#>
Prefix: o: <https://example.com/ontology#>
Ontology:
Class: beta
SubClassOf: beta, beta, beta
"};
backend_rename_helper(
ontology,
new_ontology,
Position::new(4, 7),
"https://example.com/ontology#beta",
)
.await;
}
#[test(tokio::test)]
async fn backend_rename_abbriviated_iri_should_work_for_matching() {
setup();
let ontology = indoc! {"
Prefix: o: <https://example.com/ontology#>
Ontology:
Class: o:B
SubClassOf: o:B, B, <https://example.com/ontology#B>
Class: B
"};
let new_ontology = indoc! {"
Prefix: o: <https://example.com/ontology#>
Ontology:
Class: o:beta
SubClassOf: o:beta, B, o:beta
Class: B
"};
backend_rename_helper(ontology, new_ontology, Position::new(3, 9), "beta").await;
}
#[test(tokio::test)]
async fn backend_rename_full_iri_should_work_for_matching() {
setup();
let ontology = indoc! {"
Ontology:
Class: <https://example.com/ontology#B>
SubClassOf: o:B, B, <https://example.com/ontology#B>
Class: B
Class: o:B
"};
let new_ontology = indoc! {"
Ontology:
Class: <https://example.com/ontology#beta>
SubClassOf: o:B, B, <https://example.com/ontology#beta>
Class: B
Class: o:B
"};
backend_rename_helper(
ontology,
new_ontology,
Position::new(1, 9),
"https://example.com/ontology#beta",
)
.await;
}
#[test(tokio::test)]
async fn backend_rename_full_iri_should_not_shorten() {
setup();
let ontology = indoc! {"
Prefix: o: <https://example.com/ontology#>
Prefix: : <https://example.com/ontology#>
Ontology:
Class: <https://example.com/ontology#B>
SubClassOf: o:B, B, <https://example.com/ontology#B>
Class: B
"};
let new_ontology = indoc! {"
Prefix: o: <https://example.com/ontology#>
Prefix: : <https://example.com/ontology#>
Ontology:
Class: <https://example.com/things#beta>
SubClassOf: <https://example.com/things#beta>, <https://example.com/things#beta>, <https://example.com/things#beta>
Class: <https://example.com/things#beta>
"};
backend_rename_helper(
ontology,
new_ontology,
Position::new(4, 9),
"https://example.com/things#beta",
)
.await;
}
async fn backend_rename_helper(
old_ontology: &str,
new_ontology: &str,
position: Position,
new_name: &str,
) {
let service = arrange_backend(None, vec![("https://example.com/ontology#", "dummy")]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let url = Url::from_file_path(dir.path().join("foo.omn")).unwrap();
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: old_ontology.to_string(),
},
})
.await;
let sync = service.inner().read_sync().await;
let (doc, _) = sync.get_internal_document(&url).unwrap();
let pos = position
.into_lsp(doc.rope(), &PositionEncodingKind::UTF16)
.unwrap();
drop(sync);
let result = service
.inner()
.rename(RenameParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: pos,
},
new_name: new_name.to_string(),
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
})
.await;
assert_empty_diagnostics(&service).await;
let result = result.unwrap();
let result = result.unwrap();
let changes = result.changes.unwrap();
for (url, edits) in changes {
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: url.clone(),
version: 1,
},
content_changes: edits
.iter()
.sorted_by_key(|e| e.range.start)
.rev()
.map(|e| TextDocumentContentChangeEvent {
range: Some(e.range),
range_length: None,
text: e.new_text.clone(),
})
.collect(),
})
.await;
}
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
let doc = workspace
.get_internal_document(&url.to_file_path().unwrap())
.unwrap();
let doc_content = doc.rope().to_string();
assert_eq!(doc_content, new_ontology);
}
#[test(tokio::test)]
async fn backend_did_open_should_load_external_documents_recursivly() {
let service = arrange_backend(
None,
vec![
(
"http://a/depth-1#",
indoc! {r#"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://a/depth-1#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
ontologyIRI="http://a/depth-1#">
<Prefix name="" IRI="http://a/depth-1#"/>
<Prefix name="owl" IRI="http://www.w3.org/2002/07/owl#"/>
<Prefix name="rdf" IRI="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
<Prefix name="xml" IRI="http://www.w3.org/XML/1998/namespace"/>
<Prefix name="xsd" IRI="http://www.w3.org/2001/XMLSchema#"/>
<Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>
<Prefix name="d-2" IRI="http://a/depth-2#"/>
<Declaration>
<Class IRI="Depth1Class"/>
</Declaration>
<AnnotationAssertion>
<AnnotationProperty abbreviatedIRI="d-2:Depth2Annotation"/>
<IRI>Depth1Class</IRI>
<Literal xml:lang="en">Tutor</Literal>
</AnnotationAssertion>
</Ontology>
"#},
),
(
"http://a/depth-2#",
indoc! {r#"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://a/depth-2"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
ontologyIRI="http://a/depth-2">
<Prefix name="" IRI="http://a/depth-2#"/>
<Prefix name="owl" IRI="http://www.w3.org/2002/07/owl#"/>
<Prefix name="rdf" IRI="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
<Prefix name="xml" IRI="http://www.w3.org/XML/1998/namespace"/>
<Prefix name="xsd" IRI="http://www.w3.org/2001/XMLSchema#"/>
<Prefix name="rdfs" IRI="http://www.w3.org/2000/01/rdf-schema#"/>
</Ontology>
"#},
),
],
)
.await;
let dir = TempDir::new("owl-ms-test").unwrap();
let url = Url::from_file_path(dir.path().join("foo.omn")).unwrap();
let ontology = "
Prefix: d-1: <http://a/depth-1#>
Ontology: depth-0
";
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.into(),
},
})
.await;
assert_empty_diagnostics(&service).await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
assert_eq!(workspace.external_documents().len(), 2);
}
#[test(tokio::test)]
async fn diagnostics_missing_class_with_external_document_should_report_error() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
"http://example.org/external",
"http://example.org/external.owx",
),
)]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![(
"http://example.org/external.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://example.org/external"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
ontologyIRI="http://example.org/external">
<Declaration>
<Class IRI="#DefinedClass"/>
</Declaration>
</Ontology>
"##,
)],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
let ontology = r#"
Ontology: <http://example.org/main>
Import: <http://example.org/external>
Class: LocalClass
SubClassOf: DefinedClass, UndefinedClass
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl-ms".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let diagnostics = service_diagnostics(&service).await;
let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label()).collect();
let diagnostic_text = diagnostic_labels.join("\n");
assert!(
!diagnostics.is_empty(),
"Should have at least one diagnostic for UndefinedClass"
);
assert!(
diagnostic_text.contains("UndefinedClass"),
"Diagnostic should mention UndefinedClass. Found diagnostics:\n{}",
diagnostic_text
);
assert!(
diagnostic_text.contains("used but not defined"),
"Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
diagnostic_text
);
}
#[test(tokio::test)]
async fn diagnostics_missing_datatype_with_external_document_should_report_error() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
"http://example.org/external",
"http://example.org/external.owx",
),
)]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![(
"http://example.org/external.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://example.org/external"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
ontologyIRI="http://example.org/external">
<Declaration>
<Datatype IRI="#DefinedDatatype"/>
</Declaration>
</Ontology>
"##,
)],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
let ontology = r#"
Ontology: <http://example.org/main>
Import: <http://example.org/external>
Datatype: LocalDatatype
DataProperty: hasValue
Range: DefinedDatatype, UndefinedDatatype
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl-ms".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let diagnostics = service_diagnostics(&service).await;
let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label()).collect();
let diagnostic_text = diagnostic_labels.join("\n");
assert!(
!diagnostics.is_empty(),
"Should have at least one diagnostic for UndefinedDatatype"
);
assert!(
diagnostic_text.contains("UndefinedDatatype"),
"Diagnostic should mention UndefinedDatatype. Found diagnostics:\n{}",
diagnostic_text
);
assert!(
diagnostic_text.contains("used but not defined"),
"Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
diagnostic_text
);
}
#[test(tokio::test)]
async fn diagnostics_missing_object_property_with_external_document_should_report_error() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
"http://example.org/external",
"http://example.org/external.owx",
),
)]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![(
"http://example.org/external.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://example.org/external"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
ontologyIRI="http://example.org/external">
<Declaration>
<ObjectProperty IRI="#definedProperty"/>
</Declaration>
</Ontology>
"##,
)],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
let ontology = r#"
Ontology: <http://example.org/main>
Import: <http://example.org/external>
Class: MyClass
SubClassOf: definedProperty some MyClass, undefinedProperty some MyClass
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl-ms".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let diagnostics = service_diagnostics(&service).await;
let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label()).collect();
let diagnostic_text = diagnostic_labels.join("\n");
assert!(
!diagnostics.is_empty(),
"Should have at least one diagnostic for undefinedProperty"
);
assert!(
diagnostic_text.contains("undefinedProperty"),
"Diagnostic should mention undefinedProperty. Found diagnostics:\n{}",
diagnostic_text
);
assert!(
diagnostic_text.contains("used but not defined"),
"Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
diagnostic_text
);
}
#[test(tokio::test)]
async fn diagnostics_missing_data_property_with_external_document_should_report_error() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
"http://example.org/external",
"http://example.org/external.owx",
),
)]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![(
"http://example.org/external.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://example.org/external"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
ontologyIRI="http://example.org/external">
<Declaration>
<DataProperty IRI="#definedDataProperty"/>
</Declaration>
</Ontology>
"##,
)],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
let ontology = r#"
Ontology: <http://example.org/main>
Import: <http://example.org/external>
DataProperty: localDataProperty
Class: MyClass
SubClassOf: definedDataProperty some xsd:string, undefinedDataProperty some xsd:string
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl-ms".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let diagnostics = service_diagnostics(&service).await;
let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label()).collect();
let diagnostic_text = diagnostic_labels.join("\n");
assert!(
!diagnostics.is_empty(),
"Should have at least one diagnostic for undefinedDataProperty"
);
assert!(
diagnostic_text.contains("undefinedDataProperty"),
"Diagnostic should mention undefinedDataProperty. Found diagnostics:\n{}",
diagnostic_text
);
assert!(
diagnostic_text.contains("used but not defined"),
"Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
diagnostic_text
);
}
#[test(tokio::test)]
async fn diagnostics_missing_annotation_property_with_external_document_should_report_error() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
"http://example.org/external",
"http://example.org/external.owx",
),
)]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![(
"http://example.org/external.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://example.org/external"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
ontologyIRI="http://example.org/external">
<Declaration>
<AnnotationProperty IRI="#definedAnnotation"/>
</Declaration>
</Ontology>
"##,
)],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
let ontology = r#"
Ontology: <http://example.org/main>
Import: <http://example.org/external>
AnnotationProperty: localAnnotation
Class: MyClass
Annotations:
definedAnnotation "This works",
undefinedAnnotation "This should error"
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl-ms".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let diagnostics = service_diagnostics(&service).await;
let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label()).collect();
let diagnostic_text = diagnostic_labels.join("\n");
assert!(
!diagnostics.is_empty(),
"Should have at least one diagnostic for undefinedAnnotation"
);
assert!(
diagnostic_text.contains("undefinedAnnotation"),
"Diagnostic should mention undefinedAnnotation. Found diagnostics:\n{}",
diagnostic_text
);
assert!(
diagnostic_text.contains("used but not defined"),
"Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
diagnostic_text
);
}
#[test(tokio::test)]
async fn diagnostics_missing_individual_with_external_document_should_report_error() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
"http://example.org/external",
"http://example.org/external.owx",
),
)]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![(
"http://example.org/external.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://example.org/external"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
ontologyIRI="http://example.org/external">
<Declaration>
<NamedIndividual IRI="#definedIndividual"/>
</Declaration>
</Ontology>
"##,
)],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
let ontology = r#"
Ontology: <http://example.org/main>
Import: <http://example.org/external>
Individual: localIndividual
Individual: definedIndividual
DifferentFrom: undefinedIndividual
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl-ms".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let diagnostics = service_diagnostics(&service).await;
let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label()).collect();
let diagnostic_text = diagnostic_labels.join("\n");
assert!(
!diagnostics.is_empty(),
"Should have at least one diagnostic for undefinedIndividual"
);
assert!(
diagnostic_text.contains("undefinedIndividual"),
"Diagnostic should mention undefinedIndividual. Found diagnostics:\n{}",
diagnostic_text
);
assert!(
diagnostic_text.contains("used but not defined"),
"Diagnostic should say 'used but not defined'. Found diagnostics:\n{}",
diagnostic_text
);
}
#[test(tokio::test)]
async fn diagnostics_all_defined_in_external_document_should_have_no_errors() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
"http://example.org/external",
"http://example.org/external.owx",
),
)]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![(
"http://example.org/external.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://example.org/external"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
ontologyIRI="http://example.org/external">
<Declaration>
<Class IRI="http://example.org/external/ExternalClass"/>
</Declaration>
<Declaration>
<ObjectProperty IRI="http://example.org/external/externalObjectProperty"/>
</Declaration>
<Declaration>
<NamedIndividual IRI="http://example.org/external/externalIndividual"/>
</Declaration>
</Ontology>
"##,
)],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
let ontology = r#"
Ontology: <http://example.org/main>
Import: <http://example.org/external>
Class: <http://example.org/external/ExternalClass>
Class: LocalClass
SubClassOf: <http://example.org/external/ExternalClass>
ObjectProperty: <http://example.org/external/externalObjectProperty>
Individual: <http://example.org/external/externalIndividual>
Types: LocalClass
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl-ms".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let diagnostics = service_diagnostics(&service).await;
assert_eq!(
diagnostics.len(),
0,
"Should have no diagnostics when all entities are defined in external document. Found: {:#?}",
diagnostics
);
}
#[test(tokio::test)]
async fn diagnostics_multiple_missing_iris_with_external_document_should_report_all_errors() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap()).with_uri(
"http://example.org/external",
"http://example.org/external.owx",
),
)]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![(
"http://example.org/external.owx",
r##"
<?xml version="1.0"?>
<Ontology xmlns="http://www.w3.org/2002/07/owl#"
xml:base="http://example.org/external"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
ontologyIRI="http://example.org/external">
<Declaration>
<Class IRI="#DefinedClass"/>
</Declaration>
</Ontology>
"##,
)],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
let ontology = r#"
Ontology: <http://example.org/main>
Import: <http://example.org/external>
Class: LocalClass
SubClassOf: DefinedClass, UndefinedClass1, UndefinedClass2, undefinedProperty some UndefinedClass3
"#;
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl-ms".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let diagnostics = service_diagnostics(&service).await;
assert!(
diagnostics.len() >= 4,
"Should have at least 4 diagnostics for undefined entities. Found: {}",
diagnostics.len()
);
let diagnostic_labels: Vec<String> = diagnostics.iter().map(|d| d.label()).collect();
let diagnostic_text = diagnostic_labels.join(", ");
assert!(
diagnostic_text.contains("UndefinedClass1"),
"Should report UndefinedClass1"
);
assert!(
diagnostic_text.contains("UndefinedClass2"),
"Should report UndefinedClass2"
);
assert!(
diagnostic_text.contains("UndefinedClass3"),
"Should report UndefinedClass3"
);
assert!(
diagnostic_text.contains("undefinedProperty"),
"Should report undefinedProperty"
);
}
#[test(tokio::test)]
async fn backend_goto_definition_on_import_iri_should_navigate_to_imported_file() {
setup();
let (service, tmp_dir) = arrange_multi_file_ontology().await;
let url = Url::from_file_path(tmp_dir.path().join("ontology-a").join("a1.omn")).unwrap();
let result = service
.inner()
.goto_definition(GotoDefinitionParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(4, 30), },
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
})
.await
.unwrap();
let expected_url =
Url::from_file_path(tmp_dir.path().join("ontology-a").join("a2.omn")).unwrap();
let result = result.unwrap();
match result {
GotoDefinitionResponse::Scalar(location) => {
assert_eq!(location.uri, expected_url);
assert_eq!(location.range.start.line, 0);
assert_eq!(location.range.start.character, 0);
}
_ => panic!("Expected Scalar response for import IRI goto definition"),
}
}
#[test(tokio::test)]
async fn backend_goto_definition_on_import_iri_not_in_catalog_should_return_none() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::CatalogFile(
Catalog::new(dir.join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://example.org/other.omn", "other.omn"),
)]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![("http://unknown.org/not-in-catalog.omn", "dummy")],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("test.omn")).unwrap();
let ontology = indoc! {r#"
Prefix: : <http://example.org/ontology#>
Ontology: <http://example.org/test.omn>
Import: <http://unknown.org/not-in-catalog.omn>
Class: TestClass
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let result = service
.inner()
.goto_definition(GotoDefinitionParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(2, 20), },
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
})
.await
.unwrap();
assert!(result.is_none());
}
#[test(tokio::test)]
async fn backend_goto_definition_on_import_iri_with_nested_catalog_should_work() {
setup();
let tmp_dir = arrange_workspace_folders(|dir| {
vec![WorkspaceMember::Folder {
name: "sub".into(),
children: vec![
WorkspaceMember::CatalogFile(
Catalog::new(dir.join("sub").join("catalog-v001.xml").to_str().unwrap())
.with_uri("http://example.org/imported.omn", "imported.omn"),
),
WorkspaceMember::OmnFile {
name: "imported.omn".into(),
content: indoc! {r#"
Prefix: : <http://example.org/imported#>
Ontology: <http://example.org/imported.omn>
Class: ImportedClass
"#}
.into(),
},
],
}]
});
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("sub").join("main.omn")).unwrap();
let ontology = indoc! {r#"
Prefix: : <http://example.org/main#>
Ontology: <http://example.org/main.omn>
Import: <http://example.org/imported.omn>
Class: MainClass
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let result = service
.inner()
.goto_definition(GotoDefinitionParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
position: lsp_types::Position::new(2, 20), },
work_done_progress_params: WorkDoneProgressParams {
work_done_token: None,
},
partial_result_params: PartialResultParams {
partial_result_token: None,
},
})
.await
.unwrap();
let expected_url =
Url::from_file_path(tmp_dir.path().join("sub").join("imported.omn")).unwrap();
let result = result.unwrap();
match result {
GotoDefinitionResponse::Scalar(location) => {
assert_eq!(location.uri, expected_url);
}
_ => panic!("Expected Scalar response for import IRI goto definition"),
}
}
#[test(tokio::test)]
async fn backend_code_action_on_missing_iri_should_create_frame() {
setup();
let tmp_dir = arrange_workspace_folders(|_| vec![]);
let service = arrange_backend(
Some(WorkspaceFolder {
uri: Url::from_directory_path(tmp_dir.path()).unwrap(),
name: "test workspace".into(),
}),
vec![],
)
.await;
let url = Url::from_file_path(tmp_dir.path().join("main.omn")).unwrap();
let ontology = indoc! {r#"
Prefix: : <http://example.org/main#>
Ontology: <http://example.org/main.omn>
Class: SomeClass
SubClassOf: UndefinedClass # Cursor is on UndefinedClass
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
let result = service
.inner()
.code_action(CodeActionParams {
text_document: TextDocumentIdentifier { uri: url.clone() },
range: lsp_types::Range {
start: lsp_types::Position::new(3, 26),
end: lsp_types::Position::new(3, 26),
},
context: CodeActionContext::default(),
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
})
.await
.unwrap();
let result = result.expect("Sould be some some");
assert!(result.iter().any(|action| match action {
CodeActionOrCommand::Command(_) => false,
CodeActionOrCommand::CodeAction(code_action) => {
code_action.title.to_lowercase().contains("create")
&& code_action
.edit
.as_ref()
.unwrap()
.changes
.as_ref()
.expect("Changes should have one change, the new class")
.get(&url)
.is_some()
}
}));
}
#[test(tokio::test)]
async fn backend_did_change_should_update_ontology_id() {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();
let ontology = indoc! { r#"
Ontology: <http://invalid/ontology> <http://invalid/ontology/123>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 1,
},
content_changes: vec![TextDocumentContentChangeEvent {
text: "othername".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(0, 26),
end: lsp_types::Position::new(0, 34),
}),
range_length: None,
}],
})
.await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
let document = workspace
.internal_documents()
.exactly_one()
.unwrap_or_else(|_| panic!("Multiple documents"));
assert_eq!(
document.rope().to_string(),
indoc! { r#"
Ontology: <http://invalid/othername> <http://invalid/ontology/123>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#}
);
let id = document
.queried_document
.ontology_id
.as_ref()
.unwrap()
.value();
assert_eq!(
id,
&(
"http://invalid/othername".to_string(),
Some("http://invalid/ontology/123".to_string())
)
);
}
#[test(tokio::test)]
async fn backend_did_change_with_syntax_change_should_update_ontology_id() {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();
let ontology = indoc! { r#"
Ontology: <http://invalid/ontology> <http://invalid/ontology/123>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 1,
},
content_changes: vec![TextDocumentContentChangeEvent {
text: "Class: Foo".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(0, 36),
end: lsp_types::Position::new(0, 65),
}),
range_length: None,
}],
})
.await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
let document = workspace
.internal_documents()
.exactly_one()
.unwrap_or_else(|_| panic!("Multiple documents"));
assert_eq!(
document.rope().to_string(),
indoc! { r#"
Ontology: <http://invalid/ontology> Class: Foo
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#}
);
let id = document
.queried_document
.ontology_id
.as_ref()
.unwrap()
.value();
assert_eq!(id, &("http://invalid/ontology".to_string(), None));
}
#[test(tokio::test)]
async fn backend_did_change_with_large_syntax_change_should_update_ontology_id() {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();
let ontology = indoc! { r#"
Prefix: foo: <http://invalid/foo>
Prefix: bar: <http://invalid/bar>
Ontology: <http://invalid/ontology> <http://invalid/ontology/123>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 1,
},
content_changes: vec![
TextDocumentContentChangeEvent {
text: "Ontology: <http://invalid/othername> <http://invalid/ontology/321>\n"
.into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(1, 0),
end: lsp_types::Position::new(1, 0),
}),
range_length: None,
},
TextDocumentContentChangeEvent {
text: "#".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(2, 0),
end: lsp_types::Position::new(2, 0),
}),
range_length: None,
},
TextDocumentContentChangeEvent {
text: "#".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(3, 0),
end: lsp_types::Position::new(3, 0),
}),
range_length: None,
},
],
})
.await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
let document = workspace
.internal_documents()
.exactly_one()
.unwrap_or_else(|_| panic!("Multiple documents"));
assert_eq!(
document.rope().to_string(),
indoc! { r#"
Prefix: foo: <http://invalid/foo>
Ontology: <http://invalid/othername> <http://invalid/ontology/321>
#Prefix: bar: <http://invalid/bar>
#Ontology: <http://invalid/ontology> <http://invalid/ontology/123>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#}
);
let id = document
.queried_document
.ontology_id
.as_ref()
.unwrap()
.value();
assert_eq!(
id,
&(
"http://invalid/othername".to_string(),
Some("http://invalid/ontology/321".to_string())
)
);
}
#[test(tokio::test)]
async fn backend_did_change_with_large_syntax_change_should_update_imports() {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();
let ontology = indoc! { r#"
Ontology: <http://invalid/ontology> <http://invalid/ontology/123>
Import: <http://invalid/some-other-ontology>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 1,
},
content_changes: vec![TextDocumentContentChangeEvent {
text: "#".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(1, 0),
end: lsp_types::Position::new(1, 0),
}),
range_length: None,
}],
})
.await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
let document = workspace
.internal_documents()
.exactly_one()
.unwrap_or_else(|_| panic!("Multiple documents"));
assert_eq!(
document.rope().to_string(),
indoc! { r#"
Ontology: <http://invalid/ontology> <http://invalid/ontology/123>
# Import: <http://invalid/some-other-ontology>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#}
);
let imports = document
.queried_document
.imports
.iter()
.map(|i| i.value().clone())
.collect_vec();
assert_eq!(imports, Vec::<String>::new());
}
#[test(tokio::test)]
async fn backend_did_change_with_large_syntax_change_should_update_imports_2() {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();
let ontology = indoc! { r#"
Ontology: <http://invalid/ontology> <http://invalid/ontology/123>
# Import: <http://invalid/some-other-ontology>
# Import: <http://invalid/some-other-ontology>
Import: <http://invalid/A>
# Import: <http://invalid/B>
# Import: <http://invalid/some-other-ontology>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 1,
},
content_changes: vec![
TextDocumentContentChangeEvent {
text: "".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(1, 0),
end: lsp_types::Position::new(1, 1),
}),
range_length: None,
},
TextDocumentContentChangeEvent {
text: "#".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(4, 0),
end: lsp_types::Position::new(4, 0),
}),
range_length: None,
},
TextDocumentContentChangeEvent {
text: "".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(5, 0),
end: lsp_types::Position::new(5, 1),
}),
range_length: None,
},
],
})
.await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
let document = workspace
.internal_documents()
.exactly_one()
.unwrap_or_else(|_| panic!("Multiple documents"));
assert_eq!(
document.rope().to_string(),
indoc! { r#"
Ontology: <http://invalid/ontology> <http://invalid/ontology/123>
Import: <http://invalid/some-other-ontology>
# Import: <http://invalid/some-other-ontology>
# Import: <http://invalid/A>
Import: <http://invalid/B>
# Import: <http://invalid/some-other-ontology>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#}
);
let imports = document
.queried_document
.imports
.iter()
.map(|i| i.value().clone())
.collect_vec();
assert_eq!(
imports,
vec!["http://invalid/some-other-ontology", "http://invalid/B"]
);
}
#[test(tokio::test)]
async fn backend_did_change_with_large_syntax_change_should_update_prefixes() {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();
let ontology = indoc! { r#"
# -----------
# Prefix: c: <http://invalid/some-other-ontology>
# Prefix: d: <http://invalid/some-other-ontology>
Prefix: a: <http://invalid/A>
# Prefix: b: <http://invalid/B>
# Prefix: e: <http://invalid/some-other-ontology>
Ontology: <http://invalid/ontology> <http://invalid/ontology/123>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 1,
},
content_changes: vec![
TextDocumentContentChangeEvent {
text: "".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(1, 0),
end: lsp_types::Position::new(1, 1),
}),
range_length: None,
},
TextDocumentContentChangeEvent {
text: "#".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(4, 0),
end: lsp_types::Position::new(4, 0),
}),
range_length: None,
},
TextDocumentContentChangeEvent {
text: "".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(5, 0),
end: lsp_types::Position::new(5, 1),
}),
range_length: None,
},
],
})
.await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
let document = workspace
.internal_documents()
.exactly_one()
.unwrap_or_else(|_| panic!("Multiple documents"));
assert_eq!(
document.rope().to_string(),
indoc! { r#"
# -----------
Prefix: c: <http://invalid/some-other-ontology>
# Prefix: d: <http://invalid/some-other-ontology>
# Prefix: a: <http://invalid/A>
Prefix: b: <http://invalid/B>
# Prefix: e: <http://invalid/some-other-ontology>
Ontology: <http://invalid/ontology> <http://invalid/ontology/123>
Class: SomeClass
Annotations: rdfs:label "Some Class annotation"
"#}
);
let prefixes = document
.queried_document
.prefixes
.iter()
.map(|(k, v)| (k.clone(), v.value().clone()))
.sorted() .collect_vec();
assert_eq!(
prefixes,
vec![
("b".to_string(), "http://invalid/B".to_string()),
(
"c".to_string(),
"http://invalid/some-other-ontology".to_string()
),
]
);
}
#[test(tokio::test)]
async fn backend_did_change_with_some_should_prune_diagnostics() {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-test").unwrap();
let ontology_url = Url::from_file_path(dir.path().join("file.omn")).unwrap();
let ontology = indoc! { r#"
Ontology: Dev
Class: Janek
SubClassOf: Person, Developer
Class: Person
Class: Developer
SubClassOf: Person some X
"#};
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology.to_string(),
},
})
.await;
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 1,
},
content_changes: vec![TextDocumentContentChangeEvent {
text: "".into(),
range: Some(lsp_types::Range {
start: lsp_types::Position::new(8, 32),
end: lsp_types::Position::new(8, 33),
}),
range_length: None,
}],
})
.await;
let sync = service.inner().read_sync().await;
let workspaces = sync.workspaces();
let workspace = workspaces.iter().exactly_one().unwrap();
let document = workspace
.internal_documents()
.exactly_one()
.unwrap_or_else(|_| panic!("Multiple documents"));
assert_eq!(
document.rope().to_string(),
indoc! { r#"
Ontology: Dev
Class: Janek
SubClassOf: Person, Developer
Class: Person
Class: Developer
SubClassOf: Person some
"#}
);
let diagnostics = document.diagnostics(workspace);
assert!(!diagnostics.iter().any(|d| d.label().contains('X')));
}
mod fuzz {
use crate::{
workspace::{FrameInfo, QueriedDocument},
Backend,
};
use super::{
arrange_backend, lsp_types, setup, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
LanguageServer, TempDir, TextDocumentContentChangeEvent, TextDocumentItem, Url,
VersionedTextDocumentIdentifier,
};
use indoc::indoc;
use itertools::Itertools;
use proptest::prelude::*;
use tower_lsp::LspService;
const ONTOLOGY: &str = indoc!(
r#"
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Prefix: oth: <http://invalis/other/>
Ontology: <http://example.org/fuzz-test>
Import: <http://invalid/ontology>
Class: Foo
Annotations: rdfs:label "Foo Label"
Class: Bar
Annotations: rdfs:label "Bar Label"
Class: other:Bar
Annotations: rdfs:label "Other Bar Label"
Class: <http://example.org/full/bar>
Annotations: rdfs:label "Full Bar Label"
Datatype: D
Annotations: rdfs:label "D"
SomeSyntaxError
"#
);
fn compute_end_after_insert(start_line: u32, start_col: u32, text: &str) -> (u32, u32) {
let parts: Vec<&str> = text.split('\n').collect();
if parts.len() == 1 {
(start_line, start_col + text.len() as u32)
} else {
let end_line = start_line + (parts.len() - 1) as u32;
let end_col = parts.last().unwrap().len() as u32;
(end_line, end_col)
}
}
fn insert_text_strategy() -> impl Strategy<Value = String> {
prop_oneof![
3 => "[a-zA-Z0-9 \t\n]{0,60}",
1 => "[p{L}]{0,60}",
1 => Just("\nClass: FuzzClass\n Annotations: rdfs:label \"fuzz label\"\n"
.to_string()),
1 => Just("\nClass: AnotherFuzzClass\n".to_string()),
1 => Just("\nDatatype: SomeDatatype\n".to_string()),
1 => Just("\nAnnotations: rdfs:label \"Fuzzing annotation label\"\n".to_string()),
1 => Just("\nPrefix: x: <http://invalis/x/>\"\n".to_string()),
1 => Just("#".to_string()), ]
}
type DocumentState = String;
async fn capture_document_state(
service: &LspService<Backend>,
ontology_url: &Url,
) -> DocumentState {
let sync = service.inner().read_sync().await;
let (doc, ws) = sync.get_internal_document(ontology_url).unwrap();
let initial_rope = doc.rope().to_string();
let initial_frame_infos = doc
.all_frame_infos()
.map(
|FrameInfo {
iri,
annotations,
frame_type,
definitions,
}| {
format!(
"- Frame Info for `{iri}` of kind `{frame_type}`\n - Annotations\n{annotations:#?}\n - Definitions \n{:#?}",
definitions.iter().map(|d| d.range).collect_vec()
)
},
)
.sorted()
.join("\n");
let diagnostics = doc.diagnostics(ws);
let prefixes_ = doc.prefixes().into_iter().sorted().collect_vec();
let QueriedDocument {
ontology_id,
prefixes,
imports,
} = &doc.queried_document;
let prefixes = prefixes.iter().sorted().collect_vec();
let definitions = &doc
.stage2
.definitions
.iter()
.sorted_by_key(|rb| rb.value().iri.clone());
let references = &doc.stage2.references.iter().sorted_by_key(|rb| rb.value());
let annotations = &doc.stage2.annotations.iter().sorted_by_key(|rb| rb.value());
format!("# Internal Document\n{initial_rope}\n\n---\n\n# Frame Infos\n{initial_frame_infos}\n\n# Diagnostics\n{diagnostics:#?}\n\n# Prefixes\n{prefixes_:#?}\n\n# Queried Document\n{ontology_id:#?}{prefixes:#?}{imports:#?}\n\n# Definitions\n{definitions:#?}\n\n# References\n{references:#?}\n\n# Annotations\n{annotations:#?}")
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(200))]
#[test]
fn fuzz_insert_and_undo_preserves_document(
line in 0u32..8u32,
col in 0u32..53u32,
insert_text in insert_text_strategy(),
) {
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
let (initial_state, final_state) =
rt.block_on(async {
setup();
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-fuzz").unwrap();
let ontology_url =
Url::from_file_path(dir.path().join("fuzz.omn")).unwrap();
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ONTOLOGY.to_string(),
},
})
.await;
let initial_state = capture_document_state(&service, &ontology_url).await;
let lines: Vec<&str> = ONTOLOGY.lines().collect();
let line = line.min(lines.len() as u32 - 1);
let line_len = lines[line as usize].chars().count() as u32;
let col = col.min(line_len);
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 1,
},
content_changes: vec![TextDocumentContentChangeEvent {
range: Some(lsp_types::Range {
start: lsp_types::Position::new(line, col),
end: lsp_types::Position::new(line, col),
}),
range_length: None,
text: insert_text.clone(),
}],
})
.await;
let (end_line, end_col) =
compute_end_after_insert(line, col, &insert_text);
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 2,
},
content_changes: vec![TextDocumentContentChangeEvent {
range: Some(lsp_types::Range {
start: lsp_types::Position::new(line, col),
end: lsp_types::Position::new(end_line, end_col),
}),
range_length: None,
text: String::new(),
}],
})
.await;
let final_state = capture_document_state(&service, &ontology_url).await;
(initial_state, final_state)
});
print!("{}", pretty_assertions::StrComparison::new(&initial_state, &final_state));
print!("{}", initial_state == final_state);
prop_assert!(
initial_state == final_state,
"state mismatch after insert+undo of {:?} at ({}, {})",
insert_text,
line,
col
);
}
#[test]
fn fuzz_undo_after_reopen_document(
line in 0u32..8u32,
col in 0u32..53u32,
insert_text in insert_text_strategy(),
) {
let lines: Vec<&str> = ONTOLOGY.lines().collect();
let line = line.min(lines.len() as u32 - 1);
let line_len = lines[line as usize].chars().count() as u32;
let col = col.min(line_len);
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
let (initial_state, final_state) =
rt.block_on(async {
setup();
let (initial_state, ontology_after_edit, (end_line, end_col)) = {
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-fuzz").unwrap();
let ontology_url =
Url::from_file_path(dir.path().join("fuzz.omn")).unwrap();
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ONTOLOGY.to_string(),
},
})
.await;
let initial_state = capture_document_state(&service, &ontology_url).await;
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 1,
},
content_changes: vec![TextDocumentContentChangeEvent {
range: Some(lsp_types::Range {
start: lsp_types::Position::new(line, col),
end: lsp_types::Position::new(line, col),
}),
range_length: None,
text: insert_text.clone(),
}],
})
.await;
let (end_line, end_col) =
compute_end_after_insert(line, col, &insert_text);
let ontology_after_edit = {
let sync = service.inner().read_sync().await;
let (doc, _) = sync.get_internal_document(&ontology_url).unwrap();
doc.rope().to_string()
};
(initial_state, ontology_after_edit, (end_line, end_col))
};
let service = arrange_backend(None, vec![]).await;
let dir = TempDir::new("owl-ms-fuzz").unwrap();
let ontology_url =
Url::from_file_path(dir.path().join("fuzz.omn")).unwrap();
service
.inner()
.did_open(DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: ontology_url.clone(),
language_id: "owl2md".to_string(),
version: 0,
text: ontology_after_edit,
},
})
.await;
service
.inner()
.did_change(DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: ontology_url.clone(),
version: 2,
},
content_changes: vec![TextDocumentContentChangeEvent {
range: Some(lsp_types::Range {
start: lsp_types::Position::new(line, col),
end: lsp_types::Position::new(end_line, end_col),
}),
range_length: None,
text: String::new(),
}],
})
.await;
let final_state = capture_document_state(&service, &ontology_url).await;
(initial_state, final_state)
});
print!("{}", pretty_assertions::StrComparison::new(&initial_state, &final_state));
print!("{}", initial_state == final_state);
prop_assert!(
initial_state== final_state,
"state mismatch after insert+reopen+undo of {:?} at ({}, {})",
insert_text,
line,
col
);
}
}
}