From 149366185e6f4d5b4157c31e8a1d6ebebe8ad0ba Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Mon, 12 Jul 2021 12:13:30 -0400 Subject: [PATCH] Initial release This repository was released to implement UCO Story OC-158, supporting UCO Change Proposal 23. Some resources were copied from other CASE and/or UCO repositories, and the copying was not captured in the Git history: * The Github Actions CI was adapted from CASE-Examples. * The ontology aggregation script tests/ex_ttl.py was copied and simplified from glom_rdf.py in the CASE Python Utilities repository. * The rdf-toolkit download handler and SHA-512, under lib/, were copied from the implementation in UCO. CASE's ontology repository originated this strategy, and holds the timestamp of the first recorded hash observation. References: * [UCO OC-68] (CP-23) Convert current property restrictions and domain assertions to SHACL class shapes * [UCO OC-158] UCO needs a mechanism to review inherited SHACL PropertyShapes Signed-off-by: Alex Nelson --- .github/workflows/ci.yml | 34 ++ .gitignore | 8 + Makefile | 87 ++++ README.md | 395 ++++++++++++++- README.md.in | 222 +++++++++ README.md.sed | 46 ++ case_shacl_inheritance_reviewer/__init__.py | 526 ++++++++++++++++++++ lib/Makefile | 46 ++ lib/rdf-toolkit.jar.sha512 | 1 + ontology/shacl-inheritance-review.ttl | 134 +++++ setup.cfg | 30 ++ setup.py | 17 + tests/.gitignore | 3 + tests/Makefile | 300 +++++++++++ tests/PASS_PropertyShape_inheritance.ttl | 12 + tests/PASS_PropertyShape_ontology.ttl | 69 +++ tests/PASS_class_inheritance.ttl | 12 + tests/PASS_class_ontology.ttl | 63 +++ tests/PASS_datatype_inheritance.ttl | 12 + tests/PASS_datatype_ontology.ttl | 56 +++ tests/PASS_maxCount_inheritance.ttl | 12 + tests/PASS_maxCount_ontology.ttl | 61 +++ tests/PASS_minCount_inheritance.ttl | 12 + tests/PASS_minCount_ontology.ttl | 61 +++ tests/PASS_path_inheritance.ttl | 12 + tests/PASS_path_ontology.ttl | 56 +++ tests/PASS_subprop_inheritance.ttl | 12 + tests/PASS_subprop_ontology.ttl | 141 ++++++ tests/README.md | 17 + tests/XFAIL_PropertyShape_inheritance.ttl | 104 ++++ tests/XFAIL_PropertyShape_ontology.ttl | 152 ++++++ tests/XFAIL_class_inheritance.ttl | 104 ++++ tests/XFAIL_class_ontology.ttl | 117 +++++ tests/XFAIL_datatype_inheritance.ttl | 85 ++++ tests/XFAIL_datatype_ontology.ttl | 127 +++++ tests/XFAIL_maxCount_inheritance.ttl | 111 +++++ tests/XFAIL_maxCount_ontology.ttl | 160 ++++++ tests/XFAIL_minCount_inheritance.ttl | 111 +++++ tests/XFAIL_minCount_ontology.ttl | 162 ++++++ tests/XFAIL_path_inheritance.ttl | 104 ++++ tests/XFAIL_path_ontology.ttl | 116 +++++ tests/XFAIL_subprop_inheritance.ttl | 229 +++++++++ tests/XFAIL_subprop_ontology.ttl | 258 ++++++++++ tests/ex-triangle-1-1.ttl | 17 + tests/ex-triangle-1-2.ttl | 16 + tests/ex-triangle-2.ttl | 22 + tests/ex-triangle-inheritance.ttl | 47 ++ tests/ex-triangle.ttl | 52 ++ tests/ex_ttl.py | 25 + tests/kb-test-1.ttl | 6 + tests/kb-test-2.ttl | 14 + tests/kb-test-3.ttl | 6 + tests/kb-test-4.ttl | 6 + tests/kb-test-5.ttl | 14 + tests/kb-test-6.ttl | 6 + tests/kb-triangle-1.ttl | 13 + tests/kb-triangle-2.ttl | 11 + tests/kb-triangle-3-super.ttl | 12 + tests/kb-triangle-3.ttl | 11 + tests/requirements.txt | 1 + tests/test_all.py | 282 +++++++++++ 61 files changed, 4956 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md.in create mode 100644 README.md.sed create mode 100644 case_shacl_inheritance_reviewer/__init__.py create mode 100644 lib/Makefile create mode 100644 lib/rdf-toolkit.jar.sha512 create mode 100644 ontology/shacl-inheritance-review.ttl create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/.gitignore create mode 100644 tests/Makefile create mode 100644 tests/PASS_PropertyShape_inheritance.ttl create mode 100644 tests/PASS_PropertyShape_ontology.ttl create mode 100644 tests/PASS_class_inheritance.ttl create mode 100644 tests/PASS_class_ontology.ttl create mode 100644 tests/PASS_datatype_inheritance.ttl create mode 100644 tests/PASS_datatype_ontology.ttl create mode 100644 tests/PASS_maxCount_inheritance.ttl create mode 100644 tests/PASS_maxCount_ontology.ttl create mode 100644 tests/PASS_minCount_inheritance.ttl create mode 100644 tests/PASS_minCount_ontology.ttl create mode 100644 tests/PASS_path_inheritance.ttl create mode 100644 tests/PASS_path_ontology.ttl create mode 100644 tests/PASS_subprop_inheritance.ttl create mode 100644 tests/PASS_subprop_ontology.ttl create mode 100644 tests/README.md create mode 100644 tests/XFAIL_PropertyShape_inheritance.ttl create mode 100644 tests/XFAIL_PropertyShape_ontology.ttl create mode 100644 tests/XFAIL_class_inheritance.ttl create mode 100644 tests/XFAIL_class_ontology.ttl create mode 100644 tests/XFAIL_datatype_inheritance.ttl create mode 100644 tests/XFAIL_datatype_ontology.ttl create mode 100644 tests/XFAIL_maxCount_inheritance.ttl create mode 100644 tests/XFAIL_maxCount_ontology.ttl create mode 100644 tests/XFAIL_minCount_inheritance.ttl create mode 100644 tests/XFAIL_minCount_ontology.ttl create mode 100644 tests/XFAIL_path_inheritance.ttl create mode 100644 tests/XFAIL_path_ontology.ttl create mode 100644 tests/XFAIL_subprop_inheritance.ttl create mode 100644 tests/XFAIL_subprop_ontology.ttl create mode 100644 tests/ex-triangle-1-1.ttl create mode 100644 tests/ex-triangle-1-2.ttl create mode 100644 tests/ex-triangle-2.ttl create mode 100644 tests/ex-triangle-inheritance.ttl create mode 100644 tests/ex-triangle.ttl create mode 100644 tests/ex_ttl.py create mode 100644 tests/kb-test-1.ttl create mode 100644 tests/kb-test-2.ttl create mode 100644 tests/kb-test-3.ttl create mode 100644 tests/kb-test-4.ttl create mode 100644 tests/kb-test-5.ttl create mode 100644 tests/kb-test-6.ttl create mode 100644 tests/kb-triangle-1.ttl create mode 100644 tests/kb-triangle-2.ttl create mode 100644 tests/kb-triangle-3-super.ttl create mode 100644 tests/kb-triangle-3.ttl create mode 100644 tests/requirements.txt create mode 100644 tests/test_all.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7f4dfcb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +name: Continuous Integration + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Start from clean state + run: make clean + - name: Run tests + run: make check diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5898b95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.done.log +*.egg-info +*.jar +*.swp +.generated-* +.pytest_cache +__pycache__ +catalog-v001.xml diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eddb909 --- /dev/null +++ b/Makefile @@ -0,0 +1,87 @@ +#!/usr/bin/make -f + +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +SHELL := /bin/bash + +PYTHON3 ?= $(shell which python3.9 2>/dev/null || which python3.8 2>/dev/null || which python3.7 2>/dev/null || which python3.6 2>/dev/null || which python3) + +GSED ?= $(shell which gsed 2>/dev/null || which sed) + +all: \ + README.md + +.PHONY: \ + download + +README.md: \ + .generated-README.md + diff \ + README.md \ + .generated-README.md \ + || (echo "UPDATE:Makefile:The generated README.md does not match the Git-tracked README.md. If the above reported changes look fine, run 'cp .generated-README.md README.md' to get a file ready to commit to Git." >&2 ; exit 1) + test -r $@ && touch $@ + +.generated-README.md: \ + README.md.in \ + README.md.sed \ + tests/ex-triangle-inheritance.ttl \ + tests/ex-triangle-1-1.ttl \ + tests/ex-triangle-1-2.ttl \ + tests/ex-triangle-2.ttl \ + tests/kb-test-1.ttl \ + tests/kb-test-2.ttl \ + tests/kb-test-3.ttl \ + tests/kb-test-4.ttl \ + tests/kb-test-5.ttl \ + tests/kb-test-6.ttl \ + tests/kb-triangle-1.ttl \ + tests/kb-triangle-2.ttl \ + tests/kb-triangle-3.ttl \ + tests/kb-triangle-3-super.ttl + $(GSED) \ + -f README.md.sed \ + README.md.in \ + > $@_ + mv $@_ $@ + +.lib.done.log: + $(MAKE) \ + --directory lib + touch $@ + +# After running unit tests, see if README.md needs to be regenerated. +check: \ + .lib.done.log + $(MAKE) \ + PYTHON3=$(PYTHON3) \ + --directory tests \ + check + $(MAKE) \ + README.md + +clean: + @rm -f \ + .lib.done.log + @rm -rf \ + *.egg-info \ + case_shacl_inheritance_reviewer/__pycache__ + @$(MAKE) \ + --directory tests \ + clean + +download: \ + .lib.done.log + $(MAKE) \ + PYTHON3=$(PYTHON3) \ + --directory tests \ + download diff --git a/README.md b/README.md index 0cfde0f..b35c3ce 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,393 @@ -# opensource-repo -This repository is the recommended template repository for NIST opensource contributions. +# SHACL Reviewer Utility + +This project was developed for the [CASE community](https://caseontology.org/) to review the state of it's and [UCO](https://unifiedcyberontology.org)'s [SHACL](https://www.w3.org/TR/shacl/) implementation, particularly around the appropriateness of shapes applied to subclasses. + +A deeper description of this project is in the [background](#Background) section on this page. + + +## Disclaimer + +Participation by NIST in the creation of the documentation of mentioned software is not intended to imply a recommendation or endorsement by the National Institute of Standards and Technology, nor is it intended to imply that any specific software is necessarily the best available for the purpose. + + +## Installation + +1. Clone this repository. +2. (Optional) Create and activate a virtual environment. +3. Run `pip install .` (or the name of the cloned directory instead of `.`). + +Installation is demonstrated in the `.venv.done.log` Makefile target of the [`tests/`](tests/) directory. The `--editable` flag is a development convenience and not necessary for operational usage. + + +## Usage + + +### `case_shacl_inheritance_reviewer` + +To review `sh:PropertyShape` inheritance of an ontology using SHACL, run this command: + +```bash +case_shacl_inheritance_reviewer review.ttl ontology.ttl [ontology-2.ttl ...] +``` + +Note that: +* The output file is the first argument. +* The ontology can be passed as a monolithic file or as multiple files. + +For usage in CI workflows that wish to halt on any subclass-property-shape ontology errors being encountered, the `--strict` flag should be used. + + +## Development status + +This repository follows [CASE community guidance on describing development status](https://caseontology.org/resources/software.html#development_status), by adherence to noted support requirements. + +The status of this repository is: + +4 - Beta + + +## Versioning + +This project follows [SEMVER 2.0.0](https://semver.org/) where versions are declared. + + +## Ontology versions supported + +Though this is a CASE repository, it is not a CASE data producer, or even CASE data consumer. Its role in the CASE and UCO infrastructure is an ontology review tool. Hence, this repository will not report a "Supported version" of CASE or UCO. As a historic note, it was developed to support the release of UCO 0.7.0 and CASE 0.5.0. + + +## Repository locations + +This repository is available at the following locations: +* [https://github.com/casework/CASE-Utility-SHACL-Inheritance-Reviewer](https://github.com/casework/CASE-Utility-SHACL-Inheritance-Reviewer) +* [https://github.com/usnistgov/CASE-Utilities-Python](https://github.com/usnistgov/CASE-Utility-SHACL-Inheritance-Reviewer) (a mirror) + +Releases and issue tracking will be handled at the [casework location](https://github.com/casework/CASE-Utility-SHACL-Inheritance-Reviewer). + + +## Make targets + +Some `make` targets are defined for this repository: +* `check` - Run unit tests. +* `clean` - Remove test build files, but not downloaded files. +* `download` - Download files sufficiently to run the unit tests offline. Note if you do need to work offline, be aware touching the `setup.cfg` file in the project root directory, or `tests/requirements.txt`, will trigger a virtual environment rebuild. + +Note that as with CASE and UCO community practices, a Java jar file will be downloaded to normalize generated test Turtle content. This file will be downloaded as part of the `check` and `download` targets. + + +## Background + +SHACL, [at the time of this writing](https://www.w3.org/TR/2017/REC-shacl-20170720/), exhibits a behavior that might be unexpected from ontologists or data modelers used to working with class hierarchies. A typical use case of SHACL node-shape (`sh:NodeShape`) definitions is to apply them to OWL Class (`owl:Class`) definitions. For instance, we might have a class for a triangle, with a point and method to relate triangles and points: + +```turtle +@prefix ex: . +@prefix owl: . +@prefix rdfs: . + +ex:Triangle + a owl:Class ; + . + +ex:Point + a owl:Class ; + . + +ex:hasPoint + a owl:ObjectProperty ; + rdfs:domain ex:Triangle ; + rdfs:range ex:Point ; + . +``` + +We further might want to specify that any instance of a `ex:Triangle` should have exactly three points. The SHACL mechanism to specify this is a `sh:PropertyShape` constraint: + +```turtle +@prefix ex: . +@prefix sh: . + +ex:Triangle + a sh:NodeShape ; + sh:property ex:PropertyShape-1 ; + sh:targetClass ex:Triangle ; + . + +ex:PropertyShape-1 + a sh:PropertyShape ; + sh:path ex:hasPoint ; + sh:class ex:Point ; + sh:minCount 3 ; + sh:maxCount 3 ; + . +``` + +(While it looks like the same specificaion can be made with `owl:Restriction` or `sh:PropertyShape`, their semantics differ significantly. Discussion of semantic differences is left as out of scope of this repository.) + +The above two graphs can have their triples combined together without issue, and can even validate an instance of a triangle. This example file, [`kb-triangle-1.ttl`](tests/kb-triangle-1.ttl), contains a sample triangle. + +```turtle +@prefix ex: . +@prefix kb: . + +kb:point-1 a ex:Point . +kb:point-2 a ex:Point . +kb:point-3 a ex:Point . + +kb:triangle-1 + a ex:Triangle ; + ex:hasPoint kb:point-1 ; + ex:hasPoint kb:point-2 ; + ex:hasPoint kb:point-3 ; + . +``` + +`pyshacl` will validate it against the combined example ontology. The file [`kb-test-1.ttl`](tests/kb-test-1.ttl) is generated with this command (see the [Makefile](tests/Makefile) for the full build chain and longer flag names). + +```bash +pyshacl \ + -df turtle \ + -f turtle \ + -sf turtle \ + -o kb-test-1.ttl \ + -s ex-triangle-1-2.ttl \ + kb-triangle-1.ttl +``` + +```turtle +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms true . + +``` + +Another example file, [`kb-triangle-2.ttl`](tests/kb-triangle-2.ttl), contains a sample invalid triangle, missing a third point: + +```turtle +@prefix ex: . +@prefix kb: . + +kb:point-4 a ex:Point . +kb:point-5 a ex:Point . + +kb:triangle-2 + a ex:Triangle ; + ex:hasPoint kb:point-4 ; + ex:hasPoint kb:point-5 ; + . +``` + +```bash +pyshacl \ + -df turtle \ + -f turtle \ + -sf turtle \ + -o kb-test-1.ttl \ + -s ex-triangle-1-2.ttl \ + kb-triangle-2.ttl +``` + +```turtle +@prefix ex: . +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms false ; + sh:result [ a sh:ValidationResult ; + sh:focusNode ; + sh:resultMessage "Less than 3 values on kb:triangle-2->ex:hasPoint" ; + sh:resultPath ex:hasPoint ; + sh:resultSeverity sh:Violation ; + sh:sourceConstraintComponent sh:MinCountConstraintComponent ; + sh:sourceShape ex:PropertyShape-1 ] . + +``` + +What might not be clear to modelers is that SHACL `sh:PropertyShape`s are not inherited with OWL or RDFS subclassing. It is currently the understanding of the CASE and UCO communities that this is not an issue with any implementation of SHACL (such as `pyshacl`), but is instead an intended behavior of the SHACL specification. + +To demonstrate, say the above triangle ontology sees adoption by a plotting system, and the plotting system implements a feature that handles "Squishing" a triangle into a line by making two of its vertices into the same coordinate-set. Say also their use case involves a memory-"slimming" feature and converts those two coordinate-sets into the same `ex:Point`. Say---as a last point of contrivance---that a developer was not looking at the original `ex:Triangle` specification when they decided to add [`ex-triangle-2.ttl`](tests/ex-triangle-2.ttl) to their ontology: + +```turtle +@prefix ex: . +@prefix owl: . +@prefix rdfs: . +@prefix sh: . + +ex:Triangle-but-1-dimensional + a + sh:NodeShape , + owl:Class + ; + rdfs:subClassOf ex:Triangle ; + sh:property ex:PropertyShape-2 ; + sh:targetClass ex:Triangle-but-1-dimensional ; + . + +ex:PropertyShape-2 + a sh:PropertyShape ; + sh:path ex:hasPoint ; + sh:class ex:Point ; + sh:minCount 2 ; + sh:maxCount 2 ; + . +``` + +A modeler with the last few example-blocks fresh in their memory will spot the flaw immediately. `ex:Triangle-but-1-dimensional` is also a `ex:Triangle`, explicitly so by the included statement `ex:Triangle-but-1-dimensional rdfs:subClassOf ex:Triangle .`, and thus must have exactly three `ex:Point`s. However, a curious behavior occurs when evaluating this file, [`kb-triangle-3.ttl`](tests/kb-triangle-3.ttl). + +```turtle +@prefix ex: . +@prefix kb: . + +kb:point-6 a ex:Point . +kb:point-7 a ex:Point . + +kb:triangle-3 + a ex:Triangle-but-1-dimensional ; + ex:hasPoint kb:point-6 ; + ex:hasPoint kb:point-7 ; + . +``` + +To the developer using the new ontology file, SHACL validation appears to function. + +```bash +pyshacl \ + -df turtle \ + -f turtle \ + -sf turtle \ + -o kb-test-3.ttl \ + -s ex-triangle-2.ttl \ + kb-triangle-3.ttl +``` + +The contents of [`kb-test-3.ttl`](tests/kb-test-3.ttl) are: + +```turtle +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms true . + +``` + +But confusion arises when incorporating the superclass definition and `sh:PropertyShape`: `pyshacl` reports the instance is still valid, as shown in ([`kb-test-4.ttl`](tests/kb-test-4.ttl)). + +```bash +pyshacl \ + -df turtle \ + -f turtle \ + -sf turtle \ + -o kb-test-4.ttl \ + -s ex-triangle.ttl \ + kb-triangle-3.ttl +``` + +```turtle +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms true . + +``` + +(That example, and further examples, use a combined ontology file [`ex-triangle.ttl`](tests/ex-triangle.ttl) to avoid any possible effects from partial ontology data.) + +Adding a "Reminder" triple that explicitly adds the superclass `ex:Triangle` induces `pyshacl` to report non-conformance with the `ex:Triangle` shape. The following command inspects [`kb-triangle-3-super.ttl`](tests/kb-triangle-3-super.ttl). + +```bash +pyshacl \ + -df turtle \ + -f turtle \ + -sf turtle \ + -o kb-test-5.ttl \ + -s ex-triangle.ttl \ + kb-triangle-3-super.ttl +``` + +```turtle +@prefix ex: . +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms false ; + sh:result [ a sh:ValidationResult ; + sh:focusNode ; + sh:resultMessage "Less than 3 values on kb:triangle-3->ex:hasPoint" ; + sh:resultPath ex:hasPoint ; + sh:resultSeverity sh:Violation ; + sh:sourceConstraintComponent sh:MinCountConstraintComponent ; + sh:sourceShape ex:PropertyShape-1 ] . + +``` + +No applications of flags to `pyshacl` were found to handle this class inference (as demonstrated by the recipe for [`kb-test-6.ttl`](tests/kb-test-6.ttl)), so it is currently unclear whether this is intended to be a SHACL behavior or not. + +Unfortunately, it will not always be practical or possible to place the demand on users that they generate data in manners that annotate all objects with all superclasses made explicit. + +The solution the CASE and UCO communities are taking to address this issue is propagating shapes from classes to all their subclasses, which unfortunately can be manually intensive and error-prone. Hence, this project came to be, to affirm that shapes of subclasses do not expand the set of accepted data patterns beyond superclass constraints. + +To demonstrate that the above triangle inconsistency is detected, the following shell transcript reports the inconsistencies similar to how a `sh:ValidationReport` would do so for instance data. + +```bash +case_shacl_inheritance_reviewer \ + ex-triangle-inheritance.ttl \ + ex-triangle.ttl +``` + +```turtle +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:PropertyShape-1 + a sh:PropertyShape ; + sh:class ex:Point ; + sh:maxCount "3"^^xsd:integer ; + sh:minCount "3"^^xsd:integer ; + sh:path ex:hasPoint ; + . + +ex:PropertyShape-2 + a sh:PropertyShape ; + sh:class ex:Point ; + sh:maxCount "2"^^xsd:integer ; + sh:minCount "2"^^xsd:integer ; + sh:path ex:hasPoint ; + . + +ex:Triangle + sh:property ex:PropertyShape-1 ; + . + +ex:Triangle-but-1-dimensional + sh:property ex:PropertyShape-2 ; + . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result [ + a shir:PropertyShapeComponentBroadenedError-minCount ; + rdfs:seeAlso ex:Triangle ; + sh:focusNode ex:Triangle-but-1-dimensional ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) has a lower sh:minCount." ; + sh:resultPath ex:hasPoint ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:PropertyShape-1 ; + sh:value ex:PropertyShape-2 ; + ] ; + . + +``` + +A taxonomy of error types has been developed and is stored in this repository at [`shacl-inheritance-review.ttl`](ontology/shacl-inheritance-review.ttl). This ontology file is intended to spur discussion in the broader SHACL community, and to assist with implementation of the unit tests. It is not currently intended to be a durable artifact, nor an attempt to claim the ontological prefix `shir:`. + +Code design and tests are further documented in the [`tests/`](tests/#Testing) directory. diff --git a/README.md.in b/README.md.in new file mode 100644 index 0000000..0897882 --- /dev/null +++ b/README.md.in @@ -0,0 +1,222 @@ +# SHACL Reviewer Utility + +This project was developed for the [CASE community](https://caseontology.org/) to review the state of it's and [UCO](https://unifiedcyberontology.org)'s [SHACL](https://www.w3.org/TR/shacl/) implementation, particularly around the appropriateness of shapes applied to subclasses. + +A deeper description of this project is in the [background](#Background) section on this page. + + +## Disclaimer + +Participation by NIST in the creation of the documentation of mentioned software is not intended to imply a recommendation or endorsement by the National Institute of Standards and Technology, nor is it intended to imply that any specific software is necessarily the best available for the purpose. + + +## Installation + +1. Clone this repository. +2. (Optional) Create and activate a virtual environment. +3. Run `pip install .` (or the name of the cloned directory instead of `.`). + +Installation is demonstrated in the `.venv.done.log` Makefile target of the [`tests/`](tests/) directory. The `--editable` flag is a development convenience and not necessary for operational usage. + + +## Usage + + +### `case_shacl_inheritance_reviewer` + +To review `sh:PropertyShape` inheritance of an ontology using SHACL, run this command: + +```bash +case_shacl_inheritance_reviewer review.ttl ontology.ttl [ontology-2.ttl ...] +``` + +Note that: +* The output file is the first argument. +* The ontology can be passed as a monolithic file or as multiple files. + +For usage in CI workflows that wish to halt on any subclass-property-shape ontology errors being encountered, the `--strict` flag should be used. + + +## Development status + +This repository follows [CASE community guidance on describing development status](https://caseontology.org/resources/software.html#development_status), by adherence to noted support requirements. + +The status of this repository is: + +4 - Beta + + +## Versioning + +This project follows [SEMVER 2.0.0](https://semver.org/) where versions are declared. + + +## Ontology versions supported + +Though this is a CASE repository, it is not a CASE data producer, or even CASE data consumer. Its role in the CASE and UCO infrastructure is an ontology review tool. Hence, this repository will not report a "Supported version" of CASE or UCO. As a historic note, it was developed to support the release of UCO 0.7.0 and CASE 0.5.0. + + +## Repository locations + +This repository is available at the following locations: +* [https://github.com/casework/CASE-Utility-SHACL-Inheritance-Reviewer](https://github.com/casework/CASE-Utility-SHACL-Inheritance-Reviewer) +* [https://github.com/usnistgov/CASE-Utilities-Python](https://github.com/usnistgov/CASE-Utility-SHACL-Inheritance-Reviewer) (a mirror) + +Releases and issue tracking will be handled at the [casework location](https://github.com/casework/CASE-Utility-SHACL-Inheritance-Reviewer). + + +## Make targets + +Some `make` targets are defined for this repository: +* `check` - Run unit tests. +* `clean` - Remove test build files, but not downloaded files. +* `download` - Download files sufficiently to run the unit tests offline. Note if you do need to work offline, be aware touching the `setup.cfg` file in the project root directory, or `tests/requirements.txt`, will trigger a virtual environment rebuild. + +Note that as with CASE and UCO community practices, a Java jar file will be downloaded to normalize generated test Turtle content. This file will be downloaded as part of the `check` and `download` targets. + + +## Background + +SHACL, [at the time of this writing](https://www.w3.org/TR/2017/REC-shacl-20170720/), exhibits a behavior that might be unexpected from ontologists or data modelers used to working with class hierarchies. A typical use case of SHACL node-shape (`sh:NodeShape`) definitions is to apply them to OWL Class (`owl:Class`) definitions. For instance, we might have a class for a triangle, with a point and method to relate triangles and points: + +```turtle +@EX_TRIANGLE_1_1_TTL@ +``` + +We further might want to specify that any instance of a `ex:Triangle` should have exactly three points. The SHACL mechanism to specify this is a `sh:PropertyShape` constraint: + +```turtle +@EX_TRIANGLE_1_2_TTL@ +``` + +(While it looks like the same specificaion can be made with `owl:Restriction` or `sh:PropertyShape`, their semantics differ significantly. Discussion of semantic differences is left as out of scope of this repository.) + +The above two graphs can have their triples combined together without issue, and can even validate an instance of a triangle. This example file, [`kb-triangle-1.ttl`](tests/kb-triangle-1.ttl), contains a sample triangle. + +```turtle +@KB_TRIANGLE_1_TTL@ +``` + +`pyshacl` will validate it against the combined example ontology. The file [`kb-test-1.ttl`](tests/kb-test-1.ttl) is generated with this command (see the [Makefile](tests/Makefile) for the full build chain and longer flag names). + +```bash +pyshacl \ + -df turtle \ + -f turtle \ + -sf turtle \ + -o kb-test-1.ttl \ + -s ex-triangle-1-2.ttl \ + kb-triangle-1.ttl +``` + +```turtle +@KB_TEST_1_TTL@ +``` + +Another example file, [`kb-triangle-2.ttl`](tests/kb-triangle-2.ttl), contains a sample invalid triangle, missing a third point: + +```turtle +@KB_TRIANGLE_2_TTL@ +``` + +```bash +pyshacl \ + -df turtle \ + -f turtle \ + -sf turtle \ + -o kb-test-1.ttl \ + -s ex-triangle-1-2.ttl \ + kb-triangle-2.ttl +``` + +```turtle +@KB_TEST_2_TTL@ +``` + +What might not be clear to modelers is that SHACL `sh:PropertyShape`s are not inherited with OWL or RDFS subclassing. It is currently the understanding of the CASE and UCO communities that this is not an issue with any implementation of SHACL (such as `pyshacl`), but is instead an intended behavior of the SHACL specification. + +To demonstrate, say the above triangle ontology sees adoption by a plotting system, and the plotting system implements a feature that handles "Squishing" a triangle into a line by making two of its vertices into the same coordinate-set. Say also their use case involves a memory-"slimming" feature and converts those two coordinate-sets into the same `ex:Point`. Say---as a last point of contrivance---that a developer was not looking at the original `ex:Triangle` specification when they decided to add [`ex-triangle-2.ttl`](tests/ex-triangle-2.ttl) to their ontology: + +```turtle +@EX_TRIANGLE_2_TTL@ +``` + +A modeler with the last few example-blocks fresh in their memory will spot the flaw immediately. `ex:Triangle-but-1-dimensional` is also a `ex:Triangle`, explicitly so by the included statement `ex:Triangle-but-1-dimensional rdfs:subClassOf ex:Triangle .`, and thus must have exactly three `ex:Point`s. However, a curious behavior occurs when evaluating this file, [`kb-triangle-3.ttl`](tests/kb-triangle-3.ttl). + +```turtle +@KB_TRIANGLE_3_TTL@ +``` + +To the developer using the new ontology file, SHACL validation appears to function. + +```bash +pyshacl \ + -df turtle \ + -f turtle \ + -sf turtle \ + -o kb-test-3.ttl \ + -s ex-triangle-2.ttl \ + kb-triangle-3.ttl +``` + +The contents of [`kb-test-3.ttl`](tests/kb-test-3.ttl) are: + +```turtle +@KB_TEST_3_TTL@ +``` + +But confusion arises when incorporating the superclass definition and `sh:PropertyShape`: `pyshacl` reports the instance is still valid, as shown in ([`kb-test-4.ttl`](tests/kb-test-4.ttl)). + +```bash +pyshacl \ + -df turtle \ + -f turtle \ + -sf turtle \ + -o kb-test-4.ttl \ + -s ex-triangle.ttl \ + kb-triangle-3.ttl +``` + +```turtle +@KB_TEST_4_TTL@ +``` + +(That example, and further examples, use a combined ontology file [`ex-triangle.ttl`](tests/ex-triangle.ttl) to avoid any possible effects from partial ontology data.) + +Adding a "Reminder" triple that explicitly adds the superclass `ex:Triangle` induces `pyshacl` to report non-conformance with the `ex:Triangle` shape. The following command inspects [`kb-triangle-3-super.ttl`](tests/kb-triangle-3-super.ttl). + +```bash +pyshacl \ + -df turtle \ + -f turtle \ + -sf turtle \ + -o kb-test-5.ttl \ + -s ex-triangle.ttl \ + kb-triangle-3-super.ttl +``` + +```turtle +@KB_TEST_5_TTL@ +``` + +No applications of flags to `pyshacl` were found to handle this class inference (as demonstrated by the recipe for [`kb-test-6.ttl`](tests/kb-test-6.ttl)), so it is currently unclear whether this is intended to be a SHACL behavior or not. + +Unfortunately, it will not always be practical or possible to place the demand on users that they generate data in manners that annotate all objects with all superclasses made explicit. + +The solution the CASE and UCO communities are taking to address this issue is propagating shapes from classes to all their subclasses, which unfortunately can be manually intensive and error-prone. Hence, this project came to be, to affirm that shapes of subclasses do not expand the set of accepted data patterns beyond superclass constraints. + +To demonstrate that the above triangle inconsistency is detected, the following shell transcript reports the inconsistencies similar to how a `sh:ValidationReport` would do so for instance data. + +```bash +case_shacl_inheritance_reviewer \ + ex-triangle-inheritance.ttl \ + ex-triangle.ttl +``` + +```turtle +@EX_TRIANGLE_INHERITANCE_TTL@ +``` + +A taxonomy of error types has been developed and is stored in this repository at [`shacl-inheritance-review.ttl`](ontology/shacl-inheritance-review.ttl). This ontology file is intended to spur discussion in the broader SHACL community, and to assist with implementation of the unit tests. It is not currently intended to be a durable artifact, nor an attempt to claim the ontological prefix `shir:`. + +Code design and tests are further documented in the [`tests/`](tests/#Testing) directory. diff --git a/README.md.sed b/README.md.sed new file mode 100644 index 0000000..f8f763a --- /dev/null +++ b/README.md.sed @@ -0,0 +1,46 @@ +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +/@EX_TRIANGLE_1_1_TTL@/r tests/ex-triangle-1-1.ttl +/@EX_TRIANGLE_1_1_TTL@/d + +/@EX_TRIANGLE_1_2_TTL@/r tests/ex-triangle-1-2.ttl +/@EX_TRIANGLE_1_2_TTL@/d + +/@EX_TRIANGLE_2_TTL@/r tests/ex-triangle-2.ttl +/@EX_TRIANGLE_2_TTL@/d + +/@EX_TRIANGLE_INHERITANCE_TTL@/r tests/ex-triangle-inheritance.ttl +/@EX_TRIANGLE_INHERITANCE_TTL@/d + +/@KB_TEST_1_TTL@/r tests/kb-test-1.ttl +/@KB_TEST_1_TTL@/d + +/@KB_TEST_2_TTL@/r tests/kb-test-2.ttl +/@KB_TEST_2_TTL@/d + +/@KB_TEST_3_TTL@/r tests/kb-test-3.ttl +/@KB_TEST_3_TTL@/d + +/@KB_TEST_4_TTL@/r tests/kb-test-4.ttl +/@KB_TEST_4_TTL@/d + +/@KB_TEST_5_TTL@/r tests/kb-test-5.ttl +/@KB_TEST_5_TTL@/d + +/@KB_TRIANGLE_1_TTL@/r tests/kb-triangle-1.ttl +/@KB_TRIANGLE_1_TTL@/d + +/@KB_TRIANGLE_2_TTL@/r tests/kb-triangle-2.ttl +/@KB_TRIANGLE_2_TTL@/d + +/@KB_TRIANGLE_3_TTL@/r tests/kb-triangle-3.ttl +/@KB_TRIANGLE_3_TTL@/d diff --git a/case_shacl_inheritance_reviewer/__init__.py b/case_shacl_inheritance_reviewer/__init__.py new file mode 100644 index 0000000..4273dab --- /dev/null +++ b/case_shacl_inheritance_reviewer/__init__.py @@ -0,0 +1,526 @@ +#!/usr/bin/python + +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +__version__ = "0.1.0" + +import argparse +import logging +import os + +import rdflib.plugins.sparql +import rdflib.util + +_logger = logging.getLogger(os.path.basename(__file__)) + +NS_RDF = rdflib.RDF +NS_RDFS = rdflib.RDFS +NS_SH = rdflib.SH +NS_SHIR = rdflib.Namespace("http://example.org/ontology/shacl-inheritance-review/") + +class ConformanceError(Exception): + pass + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--debug", action="store_true") + parser.add_argument("--strict", action="store_true", help="Exit in an error state if any inheritance errors are reported (i.e. if conforms==False). (The error report in out_graph will still be intact.)") + parser.add_argument("--verbose", action="store_true", help="Augment debug log messages with timestamps.") + parser.add_argument("out_graph", help="Output file. Required to not exist.") # Requirement is to prevent accidental overwrite of inputs. + parser.add_argument("in_graph", nargs="+") + args = parser.parse_args() + + if os.path.exists(args.out_graph): + raise ValueError("File found where output graph was going to be written. Please ensure first positional argument is a currently non-existent output file.") + + logging_kwargs = dict() + logging_kwargs["level"] = logging.DEBUG if args.debug else logging.INFO + logging_kwargs["format"] = "%(asctime)s:" + logging.BASIC_FORMAT if args.verbose else logging.BASIC_FORMAT + logging.basicConfig(**logging_kwargs) + + # Initialize output graph, and add carrying-documentation triple denoting what an InheritanceValidationReport is. + out_graph = rdflib.Graph() + out_graph.namespace_manager.bind("sh", NS_SH) + out_graph.namespace_manager.bind("shir", NS_SHIR) + + # Add anchoring report node. + n_report = rdflib.BNode() + out_graph.add(( + n_report, + NS_RDF.type, + NS_SHIR.InheritanceValidationReport + )) + + # Initialize and load input graph. + in_graph = rdflib.Graph() + for in_graph_filepath in args.in_graph: + _logger.debug("Loading graph in %r...", in_graph_filepath) + in_graph.parse(in_graph_filepath, format=rdflib.util.guess_format(in_graph_filepath)) + _logger.debug("Loaded.") + nsdict = {k:v for (k,v) in in_graph.namespace_manager.namespaces()} + + for prefix in nsdict: + out_graph.namespace_manager.bind(prefix, nsdict[prefix]) + + # Members: Triples, fit for argument to rdflib.Graph.triples(). + triple_patterns_to_link = set() + + # Explain known "sub-shape" issues. + # Key: String of IRI of SHIR error class. + # Value: Tuple. + # 0: Error message. + # 1: SPARQL query to find all applicable instances for error message. + error_class_iri_to_message_and_query = dict() + + message_string = "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." + query_string = """\ +SELECT ?nClassNodeShape ?nClassPropertyShape ?nClassPropertyShapePath ?nSuperclassNodeShape ?nSuperclassPropertyShape ?nSuperclassPropertyShapePath +WHERE { + ?nSuperclassNodeShape + a owl:Class ; + a sh:NodeShape ; + sh:property ?nSuperclassPropertyShape ; + . + + ?nClassNodeShape + a owl:Class ; + a sh:NodeShape ; + rdfs:subClassOf+ ?nSuperclassNodeShape ; + . + + ?nSuperclassPropertyShape + sh:path ?nSuperclassPropertyShapePath ; + . + + FILTER NOT EXISTS { + ?nClassNodeShape + sh:property/sh:path ?nClassPropertyShapePath ; + . + ?nClassPropertyShapePath + rdfs:subPropertyOf* ?nSuperclassPropertyShapePath ; + . + } +} +""" + error_class_iri_to_message_and_query[str(NS_SHIR["PropertyShapeDroppedError"])] = (message_string, query_string) + + message_string = "Subclass (sh:focusNode) has property shape (sh:value) of ancestor class (rdfs:seeAlso) that has sh:path to superproperty of ancestor class's property shape (sh:sourceShape)." + query_string = """\ +SELECT ?nClassNodeShape ?nClassPropertyShape ?nClassPropertyShapePath ?nSuperclassNodeShape ?nSuperclassPropertyShape ?nSuperclassPropertyShapePath +WHERE { + ?nSuperclassNodeShape + a owl:Class ; + a sh:NodeShape ; + sh:property ?nSuperclassPropertyShape ; + . + + ?nSuperclassPropertyShape + sh:path ?nSuperclassPropertyShapePath ; + . + + ?nClassNodeShape + a owl:Class ; + a sh:NodeShape ; + rdfs:subClassOf+ ?nSuperclassNodeShape ; + sh:property ?nClassPropertyShape ; + . + + ?nClassPropertyShape + sh:path ?nClassPropertyShapePath ; + . + + ?nSuperclassPropertyShapePath + rdfs:subPropertyOf+ ?nClassPropertyShapePath ; + . +} +""" + error_class_iri_to_message_and_query[str(NS_SHIR["PropertyShapeComponentBroadenedError-path"])] = (message_string, query_string) + + message_string = "Subclass (sh:focusNode) has property shape (sh:value) corresponding with an ancestor class's (rdfs:seeAlso) property shape (sh:sourceShape). However, the sh:class references on the two property shapes are inverted - the subclass shape's sh:class is a superclass of the ancestor class property shape's sh:class." + query_string = """\ +SELECT ?nClassNodeShape ?nClassPropertyShape ?nClassPropertyShapePath ?nSuperclassNodeShape ?nSuperclassPropertyShape ?nSuperclassPropertyShapePath +WHERE { + ?nSuperclassNodeShape + a owl:Class ; + a sh:NodeShape ; + sh:property ?nSuperclassPropertyShape ; + . + + ?nSuperclassPropertyShape + sh:class ?nSuperclassPropertyShapeClass ; + sh:path ?nSuperclassPropertyShapePath ; + . + + ?nClassNodeShape + a owl:Class ; + a sh:NodeShape ; + rdfs:subClassOf+ ?nSuperclassNodeShape ; + sh:property ?nClassPropertyShape ; + . + + ?nClassPropertyShape + sh:class ?nClassPropertyShapeClass ; + sh:path ?nClassPropertyShapePath ; + . + + ?nClassPropertyShapePath + rdfs:subPropertyOf* ?nSuperclassPropertyShapePath ; + . + + ?nSuperclassPropertyShapeClass + rdfs:subClassOf+ ?nClassPropertyShapeClass ; + . +} +""" + error_class_iri_to_message_and_query[str(NS_SHIR["PropertyShapeComponentBroadenedError-class"])] = (message_string, query_string) + + message_string = "Subclass (sh:focusNode) has property shape (sh:value) corresponding with an ancestor class's (rdfs:seeAlso) property shape (sh:sourceShape). But, the subclass's property shape is missing its sh:class." + query_string = """\ +SELECT ?nClassNodeShape ?nClassPropertyShape ?nClassPropertyShapePath ?nSuperclassNodeShape ?nSuperclassPropertyShape ?nSuperclassPropertyShapePath +WHERE { + ?nSuperclassNodeShape + a owl:Class ; + a sh:NodeShape ; + sh:property ?nSuperclassPropertyShape ; + . + + ?nSuperclassPropertyShape + sh:class ?nSuperclassPropertyShapeClass ; + sh:path ?nSuperclassPropertyShapePath ; + . + + ?nClassNodeShape + a owl:Class ; + a sh:NodeShape ; + rdfs:subClassOf+ ?nSuperclassNodeShape ; + sh:property ?nClassPropertyShape ; + . + + ?nClassPropertyShape + sh:path ?nClassPropertyShapePath ; + . + + ?nClassPropertyShapePath + rdfs:subPropertyOf* ?nSuperclassPropertyShapePath ; + . + + FILTER NOT EXISTS { + ?nClassPropertyShape + sh:class ?nClassPropertyShapeClass ; + . + } +} +""" + error_class_iri_to_message_and_query[str(NS_SHIR["PropertyShapeComponentDroppedError-class"])] = (message_string, query_string) + + message_string = "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its sh:datatype." + query_string = """\ +SELECT ?nClassNodeShape ?nClassPropertyShape ?nClassPropertyShapePath ?nSuperclassNodeShape ?nSuperclassPropertyShape ?nSuperclassPropertyShapePath +WHERE { + ?nSuperclassNodeShape + a owl:Class ; + a sh:NodeShape ; + sh:property ?nSuperclassPropertyShape ; + . + + ?nSuperclassPropertyShape + sh:datatype ?nSuperclassPropertyShapeDatatype ; + sh:path ?nSuperclassPropertyShapePath ; + . + + ?nClassNodeShape + a owl:Class ; + a sh:NodeShape ; + rdfs:subClassOf+ ?nSuperclassNodeShape ; + sh:property ?nClassPropertyShape ; + . + + ?nClassPropertyShape + sh:path ?nClassPropertyShapePath ; + . + + ?nClassPropertyShapePath + rdfs:subPropertyOf* ?nSuperclassPropertyShapePath ; + . + + FILTER NOT EXISTS { + ?nClassPropertyShape + sh:datatype ?nClassPropertyShapeDatatype ; + . + } +} +""" + error_class_iri_to_message_and_query[str(NS_SHIR["PropertyShapeComponentDroppedError-datatype"])] = (message_string, query_string) + + message_string = "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its maxCount." + query_string = """\ +SELECT ?nClassNodeShape ?nClassPropertyShape ?nClassPropertyShapePath ?nSuperclassNodeShape ?nSuperclassPropertyShape ?nSuperclassPropertyShapePath +WHERE { + ?nSuperclassNodeShape + a owl:Class ; + a sh:NodeShape ; + sh:property ?nSuperclassPropertyShape ; + . + + ?nSuperclassPropertyShape + sh:maxCount ?lSuperclassPropertyShapeMaxCount ; + sh:path ?nSuperclassPropertyShapePath ; + . + + ?nClassNodeShape + a owl:Class ; + a sh:NodeShape ; + rdfs:subClassOf+ ?nSuperclassNodeShape ; + sh:property ?nClassPropertyShape ; + . + + ?nClassPropertyShape + sh:path ?nClassPropertyShapePath ; + . + + ?nClassPropertyShapePath + rdfs:subPropertyOf* ?nSuperclassPropertyShapePath ; + . + + FILTER NOT EXISTS { + ?nClassPropertyShape + sh:maxCount ?lClassPropertyShapeMaxCount ; + . + } +} +""" + error_class_iri_to_message_and_query[str(NS_SHIR["PropertyShapeComponentDroppedError-maxCount"])] = (message_string, query_string) + + message_string = "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) has a lower sh:minCount." ; + query_string = """\ +SELECT ?nClassNodeShape ?nClassPropertyShape ?nClassPropertyShapePath ?nSuperclassNodeShape ?nSuperclassPropertyShape ?nSuperclassPropertyShapePath +WHERE { + ?nSuperclassNodeShape + a owl:Class ; + a sh:NodeShape ; + sh:property ?nSuperclassPropertyShape ; + . + + ?nSuperclassPropertyShape + sh:maxCount ?lSuperclassPropertyShapeMaxCount ; + sh:path ?nSuperclassPropertyShapePath ; + . + + ?nClassNodeShape + a owl:Class ; + a sh:NodeShape ; + rdfs:subClassOf+ ?nSuperclassNodeShape ; + sh:property ?nClassPropertyShape ; + . + + ?nClassPropertyShape + sh:maxCount ?lClassPropertyShapeMaxCount ; + sh:path ?nClassPropertyShapePath ; + . + + ?nClassPropertyShapePath + rdfs:subPropertyOf* ?nSuperclassPropertyShapePath ; + . + + FILTER (?lClassPropertyShapeMaxCount > ?lSuperclassPropertyShapeMaxCount) +} +""" + error_class_iri_to_message_and_query[str(NS_SHIR["PropertyShapeComponentBroadenedError-maxCount"])] = (message_string, query_string) + + message_string = "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) dropped its sh:minCount." + query_string = """\ +SELECT ?nClassNodeShape ?nClassPropertyShape ?nClassPropertyShapePath ?nSuperclassNodeShape ?nSuperclassPropertyShape ?nSuperclassPropertyShapePath +WHERE { + ?nSuperclassNodeShape + a owl:Class ; + a sh:NodeShape ; + sh:property ?nSuperclassPropertyShape ; + . + + ?nSuperclassPropertyShape + sh:minCount ?lSuperclassPropertyShapeMinCount ; + sh:path ?nSuperclassPropertyShapePath ; + . + + ?nClassNodeShape + a owl:Class ; + a sh:NodeShape ; + rdfs:subClassOf+ ?nSuperclassNodeShape ; + sh:property ?nClassPropertyShape ; + . + + ?nClassPropertyShape + sh:path ?nClassPropertyShapePath ; + . + + ?nClassPropertyShapePath + rdfs:subPropertyOf* ?nSuperclassPropertyShapePath ; + . + + FILTER NOT EXISTS { + ?nClassPropertyShape + sh:minCount ?lClassPropertyShapeMinCount ; + . + } +} +""" + error_class_iri_to_message_and_query[str(NS_SHIR["PropertyShapeComponentDroppedError-minCount"])] = (message_string, query_string) + + message_string = "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) has a lower sh:minCount." + query_string = """\ +SELECT ?nClassNodeShape ?nClassPropertyShape ?nClassPropertyShapePath ?nSuperclassNodeShape ?nSuperclassPropertyShape ?nSuperclassPropertyShapePath +WHERE { + ?nSuperclassNodeShape + a owl:Class ; + a sh:NodeShape ; + sh:property ?nSuperclassPropertyShape ; + . + + ?nSuperclassPropertyShape + sh:minCount ?lSuperclassPropertyShapeMinCount ; + sh:path ?nSuperclassPropertyShapePath ; + . + + ?nClassNodeShape + a owl:Class ; + a sh:NodeShape ; + rdfs:subClassOf+ ?nSuperclassNodeShape ; + sh:property ?nClassPropertyShape ; + . + + ?nClassPropertyShape + sh:minCount ?lClassPropertyShapeMinCount ; + sh:path ?nClassPropertyShapePath ; + . + + ?nClassPropertyShapePath + rdfs:subPropertyOf* ?nSuperclassPropertyShapePath ; + . + + FILTER (?lClassPropertyShapeMinCount < ?lSuperclassPropertyShapeMinCount) +} +""" + error_class_iri_to_message_and_query[str(NS_SHIR["PropertyShapeComponentBroadenedError-minCount"])] = (message_string, query_string) + + for error_class_iri in sorted(error_class_iri_to_message_and_query.keys()): + _logger.debug("error_class_iri = %r.", error_class_iri) + ( + message_string, + query_string + ) = error_class_iri_to_message_and_query[error_class_iri] + + _logger.debug("Compiling query...") + query_object = rdflib.plugins.sparql.prepareQuery(query_string, initNs=nsdict) + _logger.debug("Compiled.") + + reported_first_result = False + _logger.debug("Running query...") + for result in in_graph.query(query_object): + if not reported_first_result: + _logger.debug("Query now yielding results.") + reported_first_result = True + ( + n_class_node_shape, + n_class_property_shape, + n_class_property_shape_path, + n_superclass_node_shape, + n_superclass_property_shape, + n_superclass_property_shape_path + ) = result + if not n_class_property_shape is None: + triple_patterns_to_link.add(( + n_class_node_shape, + None, + n_class_property_shape + )) + triple_patterns_to_link.add(( + n_superclass_node_shape, + None, + n_superclass_property_shape + )) + + n_inheritance_validation_result = rdflib.BNode() + out_graph.add(( + n_report, + NS_SH.result, + n_inheritance_validation_result + )) + out_graph.add(( + n_inheritance_validation_result, + NS_RDF.type, + rdflib.URIRef(error_class_iri) + )) + out_graph.add(( + n_inheritance_validation_result, + NS_SH.focusNode, + n_class_node_shape + )) + out_graph.add(( + n_inheritance_validation_result, + NS_SH.resultPath, + n_superclass_property_shape_path + )) + if not n_class_property_shape is None: + out_graph.add(( + n_inheritance_validation_result, + NS_SH.value, + n_class_property_shape + )) + out_graph.add(( + n_inheritance_validation_result, + NS_SH.sourceShape, + n_superclass_property_shape + )) + out_graph.add(( + n_inheritance_validation_result, + NS_SH.resultMessage, + rdflib.Literal(message_string) + )) + out_graph.add(( + n_inheritance_validation_result, + NS_SH.resultSeverity, + NS_SH.Violation + )) + out_graph.add(( + n_inheritance_validation_result, + NS_RDFS.seeAlso, + n_superclass_node_shape + )) + + _logger.debug("error_class_iris reviewed.") + + for triple_pattern in triple_patterns_to_link: + for triple in in_graph.triples(triple_pattern): + out_graph.add(triple) + # Pick up all triples of pattern's Object, presumed to be a sh:PropertyNode. + for triple in in_graph.triples((triple_pattern[2], None, None)): + out_graph.add(triple) + + results_tally = len([x for x in out_graph.triples((None, NS_SH.result, None))]) + # Report (extended) conformance. + out_graph.add(( + n_report, + NS_SH.conforms, + rdflib.Literal(results_tally == 0) + )) + + out_graph.serialize(args.out_graph, rdflib.util.guess_format(args.out_graph)) + + if results_tally != 0: + count_message = "Encountered at least one shir:ShapeBroadenedError. (%d encountered.)" % results_tally + if args.strict: + raise ConformanceError(count_message) + else: + _logger.warning(count_message) + +if __name__ == "__main__": + main() diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..b65d1ff --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,46 @@ +#!/usr/bin/make -f + +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +SHELL := /bin/bash + +all: \ + rdf-toolkit.jar + +# Downloading rdf-toolkit was previously done following the directions +# at: +# https://github.com/edmcouncil/rdf-toolkit +# However, on the file becoming temporarily unavailable, CASE has placed +# a verified copy at a custom location, as a fallback for an alternative +# retrieval from an EDM Council member's repository. +# The checksum of the original file from EDM Council's build server is +# confirmed before moving file into position. (This practice will +# probably require frequent updates, unless a signed checksum for the +# jar can be retrieved somehow.) +# In case there are concerns on potentially multiple writes to the same +# file, the documentation for wget's "--output-document file" flag notes +# that "... file will be truncated immediately, and all downloaded +# content will be written there." +rdf-toolkit.jar: + test -r rdf-toolkit.jar.sha512 + # Try retrieval from Github, then from files.caseontology.org. + wget \ + --output-document $@_ \ + https://github.com/trypuz/openfibo/blob/1f9ab415e8ebd131eadcc9b0fc46241adeeb0384/etc/serialization/rdf-toolkit.jar?raw=true \ + || wget \ + --output-document $@_ \ + http://files.caseontology.org/rdf-toolkit.jar + test \ + "x$$(openssl dgst -sha512 $@_ | awk '{print($$NF)}')" \ + == \ + "x$$(head -n1 rdf-toolkit.jar.sha512)" + mv $@_ $@ diff --git a/lib/rdf-toolkit.jar.sha512 b/lib/rdf-toolkit.jar.sha512 new file mode 100644 index 0000000..4c4f5e0 --- /dev/null +++ b/lib/rdf-toolkit.jar.sha512 @@ -0,0 +1 @@ +24890b4aa484a46803841fbe5938daf60bf2d0889c0e231102c033d71cb84a2bfa8b44419df3ad896d833609afddd4b3910d2ce28660b3350cca22bea0770dad diff --git a/ontology/shacl-inheritance-review.ttl b/ontology/shacl-inheritance-review.ttl new file mode 100644 index 0000000..ba8e4ee --- /dev/null +++ b/ontology/shacl-inheritance-review.ttl @@ -0,0 +1,134 @@ +# baseURI: http://example.org/ontology/shacl-inheritance-review + +@base . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + + + a owl:Ontology ; + rdfs:label "SHACL Inheritance Review"@en ; + rdfs:comment "An ontology to review inheritance of SHACL PropertyShapes." ; + owl:imports ; + owl:ontologyIRI ; + . + +shir:InheritanceValidationReport + a owl:Class ; + rdfs:subClassOf sh:ValidationReport ; + . + +shir:PropertyShapeBroadenedError + a owl:Class ; + rdfs:subClassOf shir:ShapeBroadenedError ; + rdfs:comment "A denotation that a PropertyShape on a subclass somehow accepts a broader set than the superclass."@en ; + . + +shir:PropertyShapeComponentBroadenedError + a owl:Class ; + rdfs:subClassOf shir:PropertyShapeBroadenedError ; + . + +shir:PropertyShapeComponentBroadenedError-class + a owl:Class ; + rdfs:subClassOf shir:PropertyShapeComponentBroadenedError ; + shir:reviews sh:class ; + . + +shir:PropertyShapeComponentBroadenedError-datatype + a owl:Class ; + rdfs:subClassOf shir:PropertyShapeComponentBroadenedError ; + shir:reviews sh:datatype ; + . + +shir:PropertyShapeComponentBroadenedError-maxCount + a owl:Class ; + rdfs:subClassOf + shir:PropertyShapeComponentBroadenedError , + shir:PropertyShapeIntervalBroadenedError + ; + shir:reviews sh:maxCount ; + . + +shir:PropertyShapeComponentBroadenedError-minCount + a owl:Class ; + rdfs:subClassOf + shir:PropertyShapeComponentBroadenedError , + shir:PropertyShapeIntervalBroadenedError + ; + shir:reviews sh:minCount ; + . + +shir:PropertyShapeComponentBroadenedError-path + a owl:Class ; + rdfs:subClassOf shir:PropertyShapeComponentBroadenedError ; + shir:reviews sh:path ; + . + +shir:PropertyShapeComponentDroppedError + a owl:Class ; + rdfs:subClassOf shir:PropertyShapeBroadenedError ; + . + +shir:PropertyShapeComponentDroppedError-class + a owl:Class ; + rdfs:subClassOf shir:PropertyShapeComponentDroppedError ; + shir:reviews sh:class ; + . + +shir:PropertyShapeComponentDroppedError-datatype + a owl:Class ; + rdfs:subClassOf shir:PropertyShapeComponentDroppedError ; + shir:reviews sh:datatype ; + . + +shir:PropertyShapeComponentDroppedError-maxCount + a owl:Class ; + rdfs:subClassOf + shir:PropertyShapeComponentDroppedError , + shir:PropertyShapeIntervalBroadenedError + ; + shir:reviews sh:maxCount ; + . + +shir:PropertyShapeComponentDroppedError-minCount + a owl:Class ; + rdfs:subClassOf + shir:PropertyShapeComponentDroppedError , + shir:PropertyShapeIntervalBroadenedError + ; + shir:reviews sh:minCount ; + . + +shir:PropertyShapeDroppedError + a owl:Class ; + rdfs:subClassOf shir:ShapeBroadenedError ; + . + +shir:ShapeBroadenedError + a owl:Class ; + rdfs:subClassOf sh:ValidationResult ; + rdfs:comment "A denotation that the set of PropertyShapes on a class somehow accepts a broader set of annotations than one or more of its superclasses."@en ; + . + +shir:PropertyShapeIntervalBroadenedError + a owl:Class ; + rdfs:subClassOf shir:PropertyShapeBroadenedError ; + . + +shir:reviews + a owl:AnnotationProperty ; + rdfs:domain shir:PropertyShapeBroadenedError ; + rdfs:range rdf:Property ; + . + +shir:shouldTriggerBroadeningError + a owl:AnnotationProperty ; + rdfs:comment "This is used for unit testing."@en ; + rdfs:domain sh:NodeShape ; + rdfs:range shir:ShapeBroadenedError ; + . + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..0d6e408 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,30 @@ +[metadata] +name = case_shacl_inheritance_reviewer +version = attr: case_shacl_inheritance_reviewer.__version__ +author = Alex Nelson +author_email = alexander.nelson@nist.gov +description = A utility to review property shape inheritance in ontologies using SHACL +license_files = LICENSE +#TODO - PyPI will need a differently-written README. +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/casework/CASE-Utility-SHACL-Inheritance-Reviewer +classifiers = + Development Status :: 4 - Beta + License :: Public Domain + Operating System :: OS Independent + Programming Language :: Python :: 3 + +[options] +install_requires = + # TODO - This constraint on pyparsing can be removed when rdflib Issue #1190 is resolved. + # https://github.com/RDFLib/rdflib/issues/1190 + pyparsing < 3.0.0 + pyshacl + requests +packages = find: +python_requires = >=3.6 + +[options.entry_points] +console_scripts = + case_shacl_inheritance_reviewer = case_shacl_inheritance_reviewer:main diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3a15d74 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +import setuptools + +if __name__ == "__main__": + setuptools.setup() diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..6a1b5e7 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,3 @@ +venv +.xfail* +.xpass* diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..dcd6f60 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,300 @@ +#!/usr/bin/make -f + +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +SHELL := /bin/bash + +PYTHON3 ?= $(shell which python3.9 2>/dev/null || which python3.8 2>/dev/null || which python3.7 2>/dev/null || which python3.6 2>/dev/null || which python3) + +top_srcdir := $(shell cd .. ; pwd) + +rdf_toolkit_jar := $(top_srcdir)/lib/rdf-toolkit.jar + +# These files are needed for the top README. +all: \ + ex-triangle-inheritance.ttl \ + kb-test-1.ttl \ + kb-test-2.ttl \ + kb-test-3.ttl \ + kb-test-4.ttl \ + kb-test-5.ttl \ + kb-test-6.ttl + +.PHONY: \ + check-cli-xfails \ + check-pytest \ + download + +.PRECIOUS: \ + %_inheritance.ttl + +# rdf-toolkit.jar is used to normalize generated Turtle content, to reduce on noise in the Git history. +%_inheritance.ttl: \ + %_ontology.ttl \ + $(top_srcdir)/.lib.done.log \ + $(top_srcdir)/case_shacl_inheritance_reviewer/__init__.py \ + .venv.done.log + source venv/bin/activate \ + && case_shacl_inheritance_reviewer \ + __$@ \ + $< + java -jar $(rdf_toolkit_jar) \ + --infer-base-iri \ + --inline-blank-nodes \ + --source __$@ \ + --source-format turtle \ + --target _$@ \ + --target-format turtle + rm __$@ + mv _$@ $@ + +$(top_srcdir)/.lib.done.log: + @echo "ERROR:tests/Makefile:Please run 'make check' from the top source directory before running 'make check' under the tests directory." >&2 + exit 1 + +.venv.done.log: \ + $(top_srcdir)/setup.cfg \ + $(top_srcdir)/setup.py \ + requirements.txt + rm -rf venv + $(PYTHON3) -m venv \ + venv + source venv/bin/activate \ + && pip install \ + --upgrade \ + pip \ + setuptools + source venv/bin/activate \ + && pip install \ + --editable \ + $(top_srcdir) + source venv/bin/activate \ + && pip install \ + --requirement requirements.txt + touch $@ + +# This CLI test confirms an output file existing causes the execution to halt. +.xfail-out_graph.done.log: \ + $(top_srcdir)/case_shacl_inheritance_reviewer/__init__.py \ + .venv.done.log + rm -f .xpass-out_graph.log + ( \ + source venv/bin/activate \ + && case_shacl_inheritance_reviewer \ + Makefile \ + kb-test-1.ttl \ + && touch .xpass-out_graph.log \ + ) || true + test ! -r .xpass-out_graph.log + touch $@ + +# This CLI test confirms the 'strict' flag and a discovered shape broadening error causes the execution to halt. +.xfail-strict.done.log: \ + $(top_srcdir)/case_shacl_inheritance_reviewer/__init__.py \ + .venv.done.log \ + ex-triangle.ttl + rm -f \ + .xfail-strict.ttl \ + .xpass-strict.log + ( \ + source venv/bin/activate \ + && case_shacl_inheritance_reviewer \ + --strict \ + .xfail-strict.ttl \ + ex-triangle.ttl \ + && touch .xpass-strict.log \ + ) || true + test -r .xfail-strict.ttl + test ! -r .xpass-strict.log + touch $@ + +check: \ + check-cli-xfails \ + check-pytest + +# These two targets are made only if the program fails under expected conditions. +check-cli-xfails: \ + .xfail-out_graph.done.log \ + .xfail-strict.done.log + +check-pytest: \ + PASS_PropertyShape_inheritance.ttl \ + PASS_class_inheritance.ttl \ + PASS_datatype_inheritance.ttl \ + PASS_maxCount_inheritance.ttl \ + PASS_minCount_inheritance.ttl \ + PASS_path_inheritance.ttl \ + PASS_subprop_inheritance.ttl \ + XFAIL_PropertyShape_inheritance.ttl \ + XFAIL_class_inheritance.ttl \ + XFAIL_datatype_inheritance.ttl \ + XFAIL_maxCount_inheritance.ttl \ + XFAIL_minCount_inheritance.ttl \ + XFAIL_path_inheritance.ttl \ + XFAIL_subprop_inheritance.ttl \ + ex-triangle-inheritance.ttl \ + kb-test-1.ttl \ + kb-test-2.ttl \ + kb-test-3.ttl \ + kb-test-4.ttl \ + kb-test-5.ttl \ + kb-test-6.ttl + source venv/bin/activate \ + && pytest \ + --log-level=DEBUG \ + --verbose \ + --verbose + +clean: + @rm -rf \ + .pytest_cache \ + __pycache__ + @rm -f \ + .*.done.log \ + .xfail* \ + .xpass* \ + _* + @rm -rf \ + venv + +download: \ + .venv.done.log + +ex-triangle.ttl: \ + $(top_srcdir)/.lib.done.log \ + .venv.done.log \ + ex-triangle-1-1.ttl \ + ex-triangle-1-2.ttl \ + ex-triangle-2.ttl \ + ex_ttl.py + source venv/bin/activate \ + && python ex_ttl.py \ + __$@ \ + ex-triangle-1-1.ttl \ + ex-triangle-1-2.ttl \ + ex-triangle-2.ttl + java -jar $(rdf_toolkit_jar) \ + --infer-base-iri \ + --inline-blank-nodes \ + --source __$@ \ + --source-format turtle \ + --target _$@ \ + --target-format turtle + rm __$@ + mv _$@ $@ + +ex-triangle-inheritance.ttl: \ + $(top_srcdir)/.lib.done.log \ + $(top_srcdir)/case_shacl_inheritance_reviewer/__init__.py \ + ex-triangle.ttl + source venv/bin/activate \ + && case_shacl_inheritance_reviewer \ + __$@ \ + ex-triangle.ttl + java -jar $(rdf_toolkit_jar) \ + --infer-base-iri \ + --inline-blank-nodes \ + --source __$@ \ + --source-format turtle \ + --target _$@ \ + --target-format turtle + rm __$@ + mv _$@ $@ + +kb-test-1.ttl: \ + .venv.done.log \ + ex-triangle-1-2.ttl \ + kb-triangle-1.ttl + source venv/bin/activate \ + && pyshacl \ + --data-file-format turtle \ + --format turtle \ + --output _$@ \ + --shacl ex-triangle-1-2.ttl \ + --shacl-file-format turtle \ + kb-triangle-1.ttl + mv _$@ $@ + +kb-test-2.ttl: \ + .venv.done.log \ + ex-triangle-1-2.ttl \ + kb-triangle-2.ttl + source venv/bin/activate \ + && pyshacl \ + --data-file-format turtle \ + --format turtle \ + --output _$@ \ + --shacl ex-triangle-1-2.ttl \ + --shacl-file-format turtle \ + kb-triangle-2.ttl \ + || true #xfail + mv _$@ $@ + +kb-test-3.ttl: \ + .venv.done.log \ + ex-triangle-2.ttl \ + kb-triangle-3.ttl + source venv/bin/activate \ + && pyshacl \ + --data-file-format turtle \ + --format turtle \ + --output _$@ \ + --shacl ex-triangle-2.ttl \ + --shacl-file-format turtle \ + kb-triangle-3.ttl + mv _$@ $@ + +kb-test-4.ttl: \ + .venv.done.log \ + ex-triangle.ttl \ + kb-triangle-3.ttl + source venv/bin/activate \ + && pyshacl \ + --data-file-format turtle \ + --format turtle \ + --output _$@ \ + --shacl ex-triangle.ttl \ + --shacl-file-format turtle \ + kb-triangle-3.ttl + mv _$@ $@ + +kb-test-5.ttl: \ + .venv.done.log \ + ex-triangle.ttl \ + kb-triangle-3-super.ttl + source venv/bin/activate \ + && pyshacl \ + --data-file-format turtle \ + --format turtle \ + --output _$@ \ + --shacl ex-triangle.ttl \ + --shacl-file-format turtle \ + kb-triangle-3-super.ttl \ + || true #xfail + mv _$@ $@ + +kb-test-6.ttl: \ + .venv.done.log \ + ex-triangle.ttl \ + kb-triangle-3.ttl + source venv/bin/activate \ + && pyshacl \ + --advanced \ + --data-file-format turtle \ + --format turtle \ + --inference both \ + --iterate-rules \ + --output _$@ \ + --shacl ex-triangle.ttl \ + --shacl-file-format turtle \ + kb-triangle-3.ttl + mv _$@ $@ diff --git a/tests/PASS_PropertyShape_inheritance.ttl b/tests/PASS_PropertyShape_inheritance.ttl new file mode 100644 index 0000000..7463ae1 --- /dev/null +++ b/tests/PASS_PropertyShape_inheritance.ttl @@ -0,0 +1,12 @@ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "true"^^xsd:boolean ; + . + diff --git a/tests/PASS_PropertyShape_ontology.ttl b/tests/PASS_PropertyShape_ontology.ttl new file mode 100644 index 0000000..8f4c75d --- /dev/null +++ b/tests/PASS_PropertyShape_ontology.ttl @@ -0,0 +1,69 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property [ + sh:path ex:property-ABC ; + ] ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:A ; + sh:property + [ + sh:path ex:property-0BC ; + ] , + [ + sh:path ex:property-ABC ; + ] + ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:B ; + sh:property + [ + sh:path ex:property-00C ; + ] , + [ + sh:path ex:property-0BC ; + ] , + [ + sh:path ex:property-ABC ; + ] + ; + . + +ex:property-00C + a owl:DatatypeProperty ; + rdfs:comment "Property first appearing in PropertyShape of C." ; + . + +ex:property-0BC + a owl:DatatypeProperty ; + rdfs:comment "Property first appearing in PropertyShape of B." ; + . + +ex:property-ABC + a owl:DatatypeProperty ; + rdfs:comment "Property first appearing in PropertyShape of A." ; + . diff --git a/tests/PASS_class_inheritance.ttl b/tests/PASS_class_inheritance.ttl new file mode 100644 index 0000000..7463ae1 --- /dev/null +++ b/tests/PASS_class_inheritance.ttl @@ -0,0 +1,12 @@ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "true"^^xsd:boolean ; + . + diff --git a/tests/PASS_class_ontology.ttl b/tests/PASS_class_ontology.ttl new file mode 100644 index 0000000..202b5e9 --- /dev/null +++ b/tests/PASS_class_ontology.ttl @@ -0,0 +1,63 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property [ + sh:class ex:Class-top ; + sh:path ex:property ; + ] ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property [ + sh:class ex:Class-sub-top ; + sh:path ex:property ; + ] ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property [ + sh:class ex:Class-sub-sub-top ; + sh:path ex:property ; + ] ; + . + +ex:property + a owl:DatatypeProperty ; + . + +ex:class-top + a owl:Class ; + . + +ex:class-sub-top + a owl:Class ; + rdfs:subClassOf ex:class-top ; + . + +ex:class-sub-sub-top + a owl:Class ; + rdfs:subClassOf ex:class-sub-top ; + . + diff --git a/tests/PASS_datatype_inheritance.ttl b/tests/PASS_datatype_inheritance.ttl new file mode 100644 index 0000000..7463ae1 --- /dev/null +++ b/tests/PASS_datatype_inheritance.ttl @@ -0,0 +1,12 @@ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "true"^^xsd:boolean ; + . + diff --git a/tests/PASS_datatype_ontology.ttl b/tests/PASS_datatype_ontology.ttl new file mode 100644 index 0000000..d6d3f6e --- /dev/null +++ b/tests/PASS_datatype_ontology.ttl @@ -0,0 +1,56 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property + [ + sh:datatype ex:datatype ; + sh:path ex:property-typed ; + ] , + [ + sh:path ex:property-untyped-typed ; + ] + ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property + [ + sh:datatype ex:datatype ; + sh:path ex:property-typed ; + ] , + [ + sh:datatype ex:datatype ; + sh:path ex:property-untyped-typed ; + ] + ; + . + +ex:datatype + a rdfs:Datatype ; + . + +ex:property-typed + a owl:DatatypeProperty ; + . + +ex:property-untyped-typed + a owl:DatatypeProperty ; + . + diff --git a/tests/PASS_maxCount_inheritance.ttl b/tests/PASS_maxCount_inheritance.ttl new file mode 100644 index 0000000..7463ae1 --- /dev/null +++ b/tests/PASS_maxCount_inheritance.ttl @@ -0,0 +1,12 @@ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "true"^^xsd:boolean ; + . + diff --git a/tests/PASS_maxCount_ontology.ttl b/tests/PASS_maxCount_ontology.ttl new file mode 100644 index 0000000..fce378d --- /dev/null +++ b/tests/PASS_maxCount_ontology.ttl @@ -0,0 +1,61 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property [ + sh:path ex:property-null-max-same-narrower ; + ] ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property [ + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-same-narrower ; + ] ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property [ + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-same-narrower ; + ] ; + . + +ex:ClassD + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassC ; + sh:property [ + sh:maxCount "0"^^xsd:integer ; + sh:path ex:property-null-max-same-narrower ; + ] ; + . + +ex:property-null-max-same-narrower + a owl:DatatypeProperty ; + rdfs:comment "Property has no maxCount in PropertyShape of A, defined maximum in B, same maximum in C, lower maximum in D." ; + . + diff --git a/tests/PASS_minCount_inheritance.ttl b/tests/PASS_minCount_inheritance.ttl new file mode 100644 index 0000000..7463ae1 --- /dev/null +++ b/tests/PASS_minCount_inheritance.ttl @@ -0,0 +1,12 @@ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "true"^^xsd:boolean ; + . + diff --git a/tests/PASS_minCount_ontology.ttl b/tests/PASS_minCount_ontology.ttl new file mode 100644 index 0000000..f87b6e0 --- /dev/null +++ b/tests/PASS_minCount_ontology.ttl @@ -0,0 +1,61 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property [ + sh:path ex:property-null-min-same-narrower ; + ] ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property [ + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-same-narrower ; + ] ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property [ + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-same-narrower ; + ] ; + . + +ex:ClassD + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassC ; + sh:property [ + sh:minCount "1"^^xsd:integer ; + sh:path ex:property-null-min-same-narrower ; + ] ; + . + +ex:property-null-min-same-narrower + a owl:DatatypeProperty ; + rdfs:comment "Property has no minCount in PropertyShape of A, defined minimum in B, same minimum in C, higher minimum in D." ; + . + diff --git a/tests/PASS_path_inheritance.ttl b/tests/PASS_path_inheritance.ttl new file mode 100644 index 0000000..7463ae1 --- /dev/null +++ b/tests/PASS_path_inheritance.ttl @@ -0,0 +1,12 @@ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "true"^^xsd:boolean ; + . + diff --git a/tests/PASS_path_ontology.ttl b/tests/PASS_path_ontology.ttl new file mode 100644 index 0000000..f9bdc13 --- /dev/null +++ b/tests/PASS_path_ontology.ttl @@ -0,0 +1,56 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property [ + sh:path ex:property-top ; + ] ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property [ + sh:path ex:property-sub-top ; + ] ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property [ + sh:path ex:property-sub-sub-top + ] ; + . + +ex:property-top + a owl:DatatypeProperty ; + . + +ex:property-sub-top + a owl:DatatypeProperty ; + rdfs:subPropertyOf ex:property-top ; + . + +ex:property-sub-sub-top + a owl:DatatypeProperty ; + rdfs:subPropertyOf ex:property-sub-top ; + . + diff --git a/tests/PASS_subprop_inheritance.ttl b/tests/PASS_subprop_inheritance.ttl new file mode 100644 index 0000000..7463ae1 --- /dev/null +++ b/tests/PASS_subprop_inheritance.ttl @@ -0,0 +1,12 @@ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "true"^^xsd:boolean ; + . + diff --git a/tests/PASS_subprop_ontology.ttl b/tests/PASS_subprop_ontology.ttl new file mode 100644 index 0000000..b8fa1d7 --- /dev/null +++ b/tests/PASS_subprop_ontology.ttl @@ -0,0 +1,141 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property ex:propertyShape-DatatypeProperty-broadest ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property ex:propertyShape-DatatypeProperty-middle ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property ex:propertyShape-DatatypeProperty-narrowest ; + . + +ex:ClassD + a + owl:Class , + sh:NodeShape + ; + sh:property ex:propertyShape-ObjectProperty-broadest ; + . + +ex:ClassE + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassD ; + sh:property ex:propertyShape-ObjectProperty-middle ; + . + +ex:ClassF + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassE ; + sh:property ex:propertyShape-ObjectProperty-narrowest ; + . + +ex:ClassX + a owl:Class ; + . + +ex:ClassY + a owl:Class ; + rdfs:subClassOf ex:ClassX ; + . + +ex:propertyShape-DatatypeProperty-broadest + a sh:PropertyShape ; + sh:path ex:d-property-top ; + . + +ex:propertyShape-DatatypeProperty-middle + a sh:PropertyShape ; + sh:path ex:d-property-sub-top ; + sh:minCount 1; + sh:maxCount 4; + sh:datatype xsd:integer ; + . + +ex:propertyShape-DatatypeProperty-narrowest + a sh:PropertyShape ; + sh:path ex:d-property-sub-sub-top ; + sh:minCount 2; + sh:maxCount 3; + sh:datatype xsd:integer ; + . + +ex:propertyShape-ObjectProperty-broadest + a sh:PropertyShape ; + sh:path ex:o-property-top ; + . + +ex:propertyShape-ObjectProperty-middle + a sh:PropertyShape ; + sh:path ex:o-property-sub-top ; + sh:minCount 1; + sh:maxCount 4; + sh:class ex:ClassX ; + . + +ex:propertyShape-ObjectProperty-narrowest + a sh:PropertyShape ; + sh:path ex:o-property-sub-sub-top ; + sh:minCount 2; + sh:maxCount 3; + sh:class ex:ClassY ; + . + +ex:d-property-top + a owl:DatatypeProperty ; + . + +ex:d-property-sub-top + a owl:DatatypeProperty ; + rdfs:subPropertyOf ex:d-property-top ; + . + +ex:d-property-sub-sub-top + a owl:DatatypeProperty ; + rdfs:subPropertyOf ex:d-property-sub-top ; + . + +ex:o-property-top + a owl:ObjectProperty ; + . + +ex:o-property-sub-top + a owl:ObjectProperty ; + rdfs:subPropertyOf ex:o-property-top ; + . + +ex:o-property-sub-sub-top + a owl:ObjectProperty ; + rdfs:subPropertyOf ex:o-property-sub-top ; + . + diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..b7484ab --- /dev/null +++ b/tests/README.md @@ -0,0 +1,17 @@ +# Testing + +The tests in this directory track expected implementation behavior of SHACL, as implemented by `pyshacl`, and of the principal tool and error-taxonomy contributed by this repository, respectively `case_shacl_inheritance_reviewer` and `shir`. + +Some of the tests are based on confirming that the text written for the top repository [README](../README.md) still aligns with previously encountered behaviors. + +The remainder of the tests review constraint expansions between subclasses, for some of the `sh:PropertyShape` constraint properties, but not all. The constraints selected so far have been chosen based on usage in [CASE](https://caseontology.org/) and [UCO](https://unifiedcyberontology.org). They can be found by reviewing the ontology and data in this file for `shir:reviews` annotation properties. The function `test_coverage` in [`test_all.py`](test_all.py) double-checks the state of known implementations. + +The overall design `case_shacl_inheritance_reviewer` takes is to run SPARQL queries that find any `sh:PropertyShape` that "Expands" the set of nodes that would be validated by SHACL in a subclass beyond what its superclass had previously specified. + +Hence, the overall test design is to craft sample ontologies, one focused on each characteristic known to be able to cause an expansion. The files `PASS_*_ontology.ttl` encode sample ontologies that demonstrate subclasses with `sh:PropertyShape`s that remain the same or contract as the class hierarchy deepens. The files `XFAIL_*_ontology.ttl` implement ontologies that have some `sh:PropertyShape` that either relaxes or drops a constraint. + +The `XFAIL_*_ontology.ttl` files embed a partial representation of the error that they are expected to trigger, attached with `shir:shouldTriggerBroadeningError`. That is to say, ground truth for each test ontology's expected results is encoded as part of the test. The function `_test_inheritance_xfail_from_inlined_ground_truth` in [`test_all.py`](test_all.py) validates that exactly the recorded set of errors is triggered. + +`pyshacl` is not used to validate data based on the `XFAIL` ontologies, because the examples written for the top-level README currently show sufficiently that there is a mismatch of expectations on shape conformance of classes and subclasses. + +To see what the inheritance-validation reports look like for a detected expansion, see `XFAIL_*_inheritance.ttl`. They are generated by running `make check` in this directory, and used by `pytest`. For review purposes, they are also recorded as Git-tracked artifacts. diff --git a/tests/XFAIL_PropertyShape_inheritance.ttl b/tests/XFAIL_PropertyShape_inheritance.ttl new file mode 100644 index 0000000..93b8ea7 --- /dev/null +++ b/tests/XFAIL_PropertyShape_inheritance.ttl @@ -0,0 +1,104 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + sh:property + ex:ClassA-property-A00 , + ex:ClassA-property-A0C , + ex:ClassA-property-AB0 + ; + . + +ex:ClassA-property-A00 + sh:path ex:property-A00 ; + . + +ex:ClassA-property-A0C + sh:path ex:property-A0C ; + . + +ex:ClassA-property-AB0 + sh:path ex:property-AB0 ; + . + +ex:ClassB + sh:property + ex:ClassB-property-0B0 , + ex:ClassB-property-AB0 + ; + . + +ex:ClassB-property-0B0 + sh:path ex:property-0B0 ; + . + +ex:ClassB-property-AB0 + sh:path ex:property-AB0 ; + . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassB ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:property-A00 ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property-A00 ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassB ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:property-A0C ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property-A0C ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:property-A00 ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property-A00 ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:property-AB0 ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property-AB0 ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassB ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:property-0B0 ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassB-property-0B0 ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassB ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:property-AB0 ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassB-property-AB0 ; + ] + ; + . + diff --git a/tests/XFAIL_PropertyShape_ontology.ttl b/tests/XFAIL_PropertyShape_ontology.ttl new file mode 100644 index 0000000..8b9ad28 --- /dev/null +++ b/tests/XFAIL_PropertyShape_ontology.ttl @@ -0,0 +1,152 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property + ex:ClassA-property-A00 , + ex:ClassA-property-A0C , + ex:ClassA-property-AB0 , + [ + sh:path ex:property-ABC ; + ] + ; + . + +ex:ClassA-property-AB0 + sh:path ex:property-AB0 ; + . + +ex:ClassA-property-A00 + sh:path ex:property-A00 ; + . + +ex:ClassA-property-A0C + sh:path ex:property-A0C ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property + ex:ClassB-property-0B0 , + ex:ClassB-property-AB0 , + [ + sh:path ex:property-0BC ; + ] , + [ + sh:path ex:property-ABC ; + ] + ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:sourceShape ex:ClassA-property-A00 ; + sh:resultPath ex:property-A00 ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property-A0C ; + sh:sourceShape ex:ClassA-property-A0C ; + ] ; + . + +ex:ClassB-property-0B0 + sh:path ex:property-0B0 ; + . + +ex:ClassB-property-AB0 + sh:path ex:property-AB0 ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property + [ + sh:path ex:property-00C ; + ] , + [ + sh:path ex:property-0BC ; + ] , + [ + sh:path ex:property-A0C ; + ] , + [ + sh:path ex:property-ABC ; + ] + ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassB ; + sh:resultPath ex:property-0B0 ; + sh:sourceShape ex:ClassB-property-0B0 ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property-A00 ; + sh:sourceShape ex:ClassA-property-A00 ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassB ; + sh:resultPath ex:property-AB0 ; + sh:sourceShape ex:ClassB-property-AB0 ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property-AB0 ; + sh:sourceShape ex:ClassA-property-AB0 ; + ] ; + . + +ex:property-00C + a owl:DatatypeProperty ; + . + +ex:property-0B0 + a owl:DatatypeProperty ; + . + +ex:property-0BC + a owl:DatatypeProperty ; + . + +ex:property-A00 + a owl:DatatypeProperty ; + . + +ex:property-A0C + a owl:DatatypeProperty ; + . + +ex:property-AB0 + a owl:DatatypeProperty ; + . + +ex:property-ABC + a owl:DatatypeProperty ; + . + diff --git a/tests/XFAIL_class_inheritance.ttl b/tests/XFAIL_class_inheritance.ttl new file mode 100644 index 0000000..5954cb8 --- /dev/null +++ b/tests/XFAIL_class_inheritance.ttl @@ -0,0 +1,104 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + sh:property ex:ClassA-property ; + . + +ex:ClassA-property + sh:class ex:Class-sub-top ; + sh:path ex:property ; + . + +ex:ClassB + sh:property ex:ClassB-property ; + . + +ex:ClassB-property + sh:class ex:Class-sub-sub-top ; + sh:path ex:property ; + . + +ex:ClassC + sh:property [ + sh:class ex:Class-top ; + sh:path ex:property ; + ] ; + . + +ex:ClassD + sh:property [ + sh:class ex:Class-top ; + sh:path ex:property ; + ] ; + . + +ex:ClassE + sh:property [ + sh:path ex:property ; + ] ; + . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result + [ + a shir:PropertyShapeComponentBroadenedError-class ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) corresponding with an ancestor class's (rdfs:seeAlso) property shape (sh:sourceShape). However, the sh:class references on the two property shapes are inverted - the subclass shape's sh:class is a superclass of the ancestor class property shape's sh:class." ; + sh:resultPath ex:property ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property ; + sh:value [ + sh:class ex:Class-top ; + sh:path ex:property ; + ] ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-class ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) corresponding with an ancestor class's (rdfs:seeAlso) property shape (sh:sourceShape). However, the sh:class references on the two property shapes are inverted - the subclass shape's sh:class is a superclass of the ancestor class property shape's sh:class." ; + sh:resultPath ex:property ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property ; + sh:value [ + sh:class ex:Class-top ; + sh:path ex:property ; + ] ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-class ; + rdfs:seeAlso ex:ClassB ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) corresponding with an ancestor class's (rdfs:seeAlso) property shape (sh:sourceShape). However, the sh:class references on the two property shapes are inverted - the subclass shape's sh:class is a superclass of the ancestor class property shape's sh:class." ; + sh:resultPath ex:property ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassB-property ; + sh:value [ + sh:class ex:Class-top ; + sh:path ex:property ; + ] ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-class ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassE ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) corresponding with an ancestor class's (rdfs:seeAlso) property shape (sh:sourceShape). But, the subclass's property shape is missing its sh:class." ; + sh:resultPath ex:property ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property ; + sh:value [ + sh:path ex:property ; + ] ; + ] + ; + . + diff --git a/tests/XFAIL_class_ontology.ttl b/tests/XFAIL_class_ontology.ttl new file mode 100644 index 0000000..1cf1da1 --- /dev/null +++ b/tests/XFAIL_class_ontology.ttl @@ -0,0 +1,117 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property ex:ClassA-property ; + . + +ex:ClassA-property + sh:class ex:Class-sub-top ; + sh:path ex:property ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property ex:ClassB-property ; + . + +ex:ClassB-property ; + sh:class ex:Class-sub-sub-top ; + sh:path ex:property ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property [ + sh:class ex:Class-top ; + sh:path ex:property ; + ] ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeComponentBroadenedError-class ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property ; + sh:sourceShape ex:ClassA-property ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-class ; + rdfs:seeAlso ex:ClassB ; + sh:resultPath ex:property ; + sh:sourceShape ex:ClassB-property ; + ] + ; + . + +ex:ClassD + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property [ + sh:class ex:Class-top ; + sh:path ex:property ; + ] ; + shir:shouldTriggerBroadeningError [ + a shir:PropertyShapeComponentBroadenedError-class ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property ; + sh:sourceShape ex:ClassA-property ; + ] ; + . + +ex:ClassE + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property [ + sh:path ex:property ; + ] ; + shir:shouldTriggerBroadeningError [ + a shir:PropertyShapeComponentDroppedError-class ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property ; + sh:sourceShape ex:ClassA-property ; + ] ; + . + +ex:property + a owl:DatatypeProperty ; + . + +ex:Class-top + a owl:Class ; + . + +ex:Class-sub-top + a owl:Class ; + rdfs:subClassOf ex:Class-top ; + . + +ex:Class-sub-sub-top + a owl:Class ; + rdfs:subClassOf ex:Class-sub-top ; + . + diff --git a/tests/XFAIL_datatype_inheritance.ttl b/tests/XFAIL_datatype_inheritance.ttl new file mode 100644 index 0000000..281743e --- /dev/null +++ b/tests/XFAIL_datatype_inheritance.ttl @@ -0,0 +1,85 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + sh:property ex:ClassA-property-typed-untyped ; + . + +ex:ClassA-property-typed-untyped + sh:datatype ex:datatype ; + sh:path ex:property-typed-untyped ; + . + +ex:ClassB + sh:property + ex:ClassB-property-untyped-typed-untyped , + [ + sh:path ex:property-typed-untyped ; + ] + ; + . + +ex:ClassB-property-untyped-typed-untyped + sh:datatype ex:datatype ; + sh:path ex:property-untyped-typed-untyped ; + . + +ex:ClassC + sh:property + [ + sh:path ex:property-typed-untyped ; + ] , + [ + sh:path ex:property-untyped-typed-untyped ; + ] + ; + . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result + [ + a shir:PropertyShapeComponentDroppedError-datatype ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassB ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its sh:datatype." ; + sh:resultPath ex:property-typed-untyped ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property-typed-untyped ; + sh:value [ + sh:path ex:property-typed-untyped ; + ] ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-datatype ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its sh:datatype." ; + sh:resultPath ex:property-typed-untyped ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property-typed-untyped ; + sh:value [ + sh:path ex:property-typed-untyped ; + ] ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-datatype ; + rdfs:seeAlso ex:ClassB ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its sh:datatype." ; + sh:resultPath ex:property-untyped-typed-untyped ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassB-property-untyped-typed-untyped ; + sh:value [ + sh:path ex:property-untyped-typed-untyped ; + ] ; + ] + ; + . + diff --git a/tests/XFAIL_datatype_ontology.ttl b/tests/XFAIL_datatype_ontology.ttl new file mode 100644 index 0000000..90b6e26 --- /dev/null +++ b/tests/XFAIL_datatype_ontology.ttl @@ -0,0 +1,127 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property + ex:ClassA-property-typed-untyped , + [ + sh:datatype ex:datatype ; + sh:path ex:property-typed ; + ] , + [ + sh:path ex:property-untyped-typed ; + ] , + [ + sh:path ex:property-untyped-typed ; + ] + ; + . + +ex:ClassA-property-typed-untyped + sh:datatype ex:datatype ; + sh:path ex:property-typed-untyped ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property + ex:ClassB-property-untyped-typed-untyped , + [ + sh:datatype ex:datatype ; + sh:path ex:property-typed ; + ] , + [ + sh:path ex:property-typed-untyped ; + ] , + [ + sh:datatype ex:datatype ; + sh:path ex:property-untyped-typed ; + ] + ; + shir:shouldTriggerBroadeningError [ + a shir:PropertyShapeComponentDroppedError-datatype ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property-typed-untyped ; + sh:sourceShape ex:ClassA-property-typed-untyped ; + ] ; + . + +ex:ClassB-property-untyped-typed-untyped + sh:datatype ex:datatype ; + sh:path ex:property-untyped-typed-untyped ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property + [ + sh:datatype ex:datatype ; + sh:path ex:property-typed ; + ] , + [ + sh:path ex:property-typed-untyped ; + ] , + [ + sh:datatype ex:datatype ; + sh:path ex:property-untyped-typed ; + ] , + [ + sh:path ex:property-untyped-typed-untyped ; + ] + ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeComponentDroppedError-datatype ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property-typed-untyped ; + sh:sourceShape ex:ClassA-property-typed-untyped ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-datatype ; + rdfs:seeAlso ex:ClassB ; + sh:resultPath ex:property-untyped-typed-untyped ; + sh:sourceShape ex:ClassB-property-untyped-typed-untyped ; + ] + ; + . + +ex:datatype + a rdfs:Datatype ; + . + +ex:property-typed + a owl:DatatypeProperty ; + . + +ex:property-typed-untyped + a owl:DatatypeProperty ; + . + +ex:property-untyped-typed + a owl:DatatypeProperty ; + . + +ex:property-untyped-typed-untyped + a owl:DatatypeProperty ; + . + diff --git a/tests/XFAIL_maxCount_inheritance.ttl b/tests/XFAIL_maxCount_inheritance.ttl new file mode 100644 index 0000000..70b9474 --- /dev/null +++ b/tests/XFAIL_maxCount_inheritance.ttl @@ -0,0 +1,111 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassB + sh:property ex:ClassB-property-null-max-same-null ; + . + +ex:ClassB-property-null-max-same-null + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-same-null ; + . + +ex:ClassC + sh:property + ex:ClassC-property-null-max-broader , + ex:ClassC-property-null-max-null , + ex:ClassC-property-null-max-same-null + ; + . + +ex:ClassC-property-null-max-broader + sh:maxCount "0"^^xsd:integer ; + sh:path ex:property-null-max-broader ; + . + +ex:ClassC-property-null-max-null + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-null ; + . + +ex:ClassC-property-null-max-same-null + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-same-null ; + . + +ex:ClassD + sh:property + [ + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-broader ; + ] , + [ + sh:path ex:property-null-max-null ; + ] , + [ + sh:path ex:property-null-max-same-null ; + ] + ; + . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result + [ + a shir:PropertyShapeComponentBroadenedError-maxCount ; + rdfs:seeAlso ex:ClassC ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) has a lower sh:minCount." ; + sh:resultPath ex:property-null-max-broader ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassC-property-null-max-broader ; + sh:value [ + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-broader ; + ] ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-maxCount ; + rdfs:seeAlso ex:ClassB ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its maxCount." ; + sh:resultPath ex:property-null-max-same-null ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassB-property-null-max-same-null ; + sh:value [ + sh:path ex:property-null-max-same-null ; + ] ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-maxCount ; + rdfs:seeAlso ex:ClassC ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its maxCount." ; + sh:resultPath ex:property-null-max-null ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassC-property-null-max-null ; + sh:value [ + sh:path ex:property-null-max-null ; + ] ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-maxCount ; + rdfs:seeAlso ex:ClassC ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its maxCount." ; + sh:resultPath ex:property-null-max-same-null ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassC-property-null-max-same-null ; + sh:value [ + sh:path ex:property-null-max-same-null ; + ] ; + ] + ; + . + diff --git a/tests/XFAIL_maxCount_ontology.ttl b/tests/XFAIL_maxCount_ontology.ttl new file mode 100644 index 0000000..3bfb5cb --- /dev/null +++ b/tests/XFAIL_maxCount_ontology.ttl @@ -0,0 +1,160 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property + [ + sh:path ex:property-null-max-broader ; + ] , + [ + sh:path ex:property-null-max-null ; + ] , + [ + sh:path ex:property-null-max-same-null ; + ] , + [ + sh:path ex:property-null-max-same-narrower ; + ] + ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property + ex:ClassB-property-null-max-same-null , + [ + sh:path ex:property-null-max-broader ; + ] , + [ + sh:path ex:property-null-max-null ; + ] , + [ + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-same-narrower ; + ] + ; + . + +ex:ClassB-property-null-max-same-null + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-same-null ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property + ex:ClassC-property-null-max-broader , + ex:ClassC-property-null-max-null , + ex:ClassC-property-null-max-same-null , + [ + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-same-narrower ; + ] + ; + . + +ex:ClassC-property-null-max-broader + sh:maxCount "0"^^xsd:integer ; + sh:path ex:property-null-max-broader ; + . + +ex:ClassC-property-null-max-null + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-null ; + . + +ex:ClassC-property-null-max-same-null + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-same-null ; + . + +ex:ClassD + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassC ; + sh:property + [ + sh:maxCount "1"^^xsd:integer ; + sh:path ex:property-null-max-broader ; + ] , + [ + sh:path ex:property-null-max-null ; + ] , + [ + sh:path ex:property-null-max-same-null ; + ] , + [ + sh:maxCount "0"^^xsd:integer ; + sh:path ex:property-null-max-same-narrower ; + ] + ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeComponentBroadenedError-maxCount ; + rdfs:seeAlso ex:ClassC ; + sh:resultPath ex:property-null-max-broader ; + sh:sourceShape ex:ClassC-property-null-max-broader ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-maxCount ; + rdfs:seeAlso ex:ClassC ; + sh:resultPath ex:property-null-max-null ; + sh:sourceShape ex:ClassC-property-null-max-null ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-maxCount ; + rdfs:seeAlso ex:ClassB ; + sh:sourceShape ex:ClassB-property-null-max-same-null ; + sh:resultPath ex:property-null-max-same-null ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-maxCount ; + rdfs:seeAlso ex:ClassC ; + sh:resultPath ex:property-null-max-same-null ; + sh:sourceShape ex:ClassC-property-null-max-same-null ; + ] + ; + . + +ex:property-null-max-broader + a owl:DatatypeProperty ; + rdfs:comment "Property has no maxCount in PropertyShape of A or B, defined maximum in C, higher maximum in D." ; + . + +ex:property-null-max-null + a owl:DatatypeProperty ; + rdfs:comment "Property has no maxCount in PropertyShape of A or B, defined maximum in C, no maximum in D." ; + . + +ex:property-null-max-same-null + a owl:DatatypeProperty ; + rdfs:comment "Property has no maxCount in PropertyShape of A, defined maximum in B, same maximum in C, no maximum in D." ; + . + +ex:property-null-max-same-narrower + a owl:DatatypeProperty ; + rdfs:comment "Property has no maxCount in PropertyShape of A, defined maximum in B, same maximum in C, lower maximum in D." ; + . + diff --git a/tests/XFAIL_minCount_inheritance.ttl b/tests/XFAIL_minCount_inheritance.ttl new file mode 100644 index 0000000..45381d8 --- /dev/null +++ b/tests/XFAIL_minCount_inheritance.ttl @@ -0,0 +1,111 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassB + sh:property ex:ClassB-property-null-min-same-null ; + . + +ex:ClassB-property-null-min-same-null + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-same-null ; + . + +ex:ClassC + sh:property + ex:ClassC-property-null-min-broader , + ex:ClassC-property-null-min-null , + ex:ClassC-property-null-min-same-null + ; + . + +ex:ClassC-property-null-min-broader + sh:minCount "1"^^xsd:integer ; + sh:path ex:property-null-min-broader ; + . + +ex:ClassC-property-null-min-null + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-null ; + . + +ex:ClassC-property-null-min-same-null + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-same-null ; + . + +ex:ClassD + sh:property + [ + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-broader ; + ] , + [ + sh:path ex:property-null-min-null ; + ] , + [ + sh:path ex:property-null-min-same-null ; + ] + ; + . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result + [ + a shir:PropertyShapeComponentBroadenedError-minCount ; + rdfs:seeAlso ex:ClassC ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) has a lower sh:minCount." ; + sh:resultPath ex:property-null-min-broader ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassC-property-null-min-broader ; + sh:value [ + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-broader ; + ] ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-minCount ; + rdfs:seeAlso ex:ClassB ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) dropped its sh:minCount." ; + sh:resultPath ex:property-null-min-same-null ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassB-property-null-min-same-null ; + sh:value [ + sh:path ex:property-null-min-same-null ; + ] ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-minCount ; + rdfs:seeAlso ex:ClassC ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) dropped its sh:minCount." ; + sh:resultPath ex:property-null-min-null ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassC-property-null-min-null ; + sh:value [ + sh:path ex:property-null-min-null ; + ] ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-minCount ; + rdfs:seeAlso ex:ClassC ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) dropped its sh:minCount." ; + sh:resultPath ex:property-null-min-same-null ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassC-property-null-min-same-null ; + sh:value [ + sh:path ex:property-null-min-same-null ; + ] ; + ] + ; + . + diff --git a/tests/XFAIL_minCount_ontology.ttl b/tests/XFAIL_minCount_ontology.ttl new file mode 100644 index 0000000..e1557e1 --- /dev/null +++ b/tests/XFAIL_minCount_ontology.ttl @@ -0,0 +1,162 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property + [ + sh:path ex:property-null-min-broader ; + ] , + [ + sh:path ex:property-null-min-null ; + ] , + [ + sh:path ex:property-null-min-same-null ; + ] , + [ + sh:path ex:property-null-min-same-narrower ; + ] + ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property + [ + sh:path ex:property-null-min-broader ; + ] , + [ + sh:path ex:property-null-min-null ; + ] , + ex:ClassB-property-null-min-same-null , + [ + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-same-narrower ; + ] + ; + . + +ex:ClassB-property-null-min-same-null + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-same-null ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property + ex:ClassC-property-null-min-broader , + ex:ClassC-property-null-min-null , + ex:ClassC-property-null-min-same-narrower , + ex:ClassC-property-null-min-same-null + ; + . + +ex:ClassC-property-null-min-broader + sh:minCount "1"^^xsd:integer ; + sh:path ex:property-null-min-broader ; + . + +ex:ClassC-property-null-min-null + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-null ; + . + +ex:ClassC-property-null-min-same-narrower + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-same-narrower ; + . + +ex:ClassC-property-null-min-same-null + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-same-null ; + . + +ex:ClassD + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassC ; + sh:property + [ + sh:minCount "0"^^xsd:integer ; + sh:path ex:property-null-min-broader ; + ] , + [ + sh:path ex:property-null-min-null ; + ] , + [ + sh:path ex:property-null-min-same-null ; + ] , + [ + sh:minCount "1"^^xsd:integer ; + sh:path ex:property-null-min-same-narrower ; + ] + ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeComponentBroadenedError-minCount ; + rdfs:seeAlso ex:ClassC ; + sh:resultPath ex:property-null-min-broader ; + sh:sourceShape ex:ClassC-property-null-min-broader ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-minCount ; + rdfs:seeAlso ex:ClassC ; + sh:resultPath ex:property-null-min-null ; + sh:sourceShape ex:ClassC-property-null-min-null ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-minCount ; + rdfs:seeAlso ex:ClassB ; + sh:resultPath ex:property-null-min-same-null ; + sh:sourceShape ex:ClassB-property-null-min-same-null ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-minCount ; + rdfs:seeAlso ex:ClassC ; + sh:resultPath ex:property-null-min-same-null ; + sh:sourceShape ex:ClassC-property-null-min-same-null ; + ] + ; + . + +ex:property-null-min-broader + a owl:DatatypeProperty ; + rdfs:comment "Property has no minCount in PropertyShape of A or B, defined minimum in C, lower minimum in D." ; + . + +ex:property-null-min-null + a owl:DatatypeProperty ; + rdfs:comment "Property has no minCount in PropertyShape of A or B, defined minimum in C, no minimum in D." ; + . + +ex:property-null-min-same-null + a owl:DatatypeProperty ; + rdfs:comment "Property has no minCount in PropertyShape of A, defined minimum in B, same minimum in C, no minimum in D." ; + . + +ex:property-null-min-same-narrower + a owl:DatatypeProperty ; + rdfs:comment "Property has no minCount in PropertyShape of A, defined minimum in B, same minimum in C, higher minimum in D." ; + . + diff --git a/tests/XFAIL_path_inheritance.ttl b/tests/XFAIL_path_inheritance.ttl new file mode 100644 index 0000000..5a0f74f --- /dev/null +++ b/tests/XFAIL_path_inheritance.ttl @@ -0,0 +1,104 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + sh:property ex:ClassA-property-sub-top ; + . + +ex:ClassA-property-sub-top + sh:path ex:property-sub-top ; + . + +ex:ClassB + sh:property ex:ClassB-property-sub-sub-top ; + . + +ex:ClassB-property-sub-sub-top + sh:path ex:property-sub-sub-top ; + . + +ex:ClassC + sh:property ex:ClassC-property-top ; + . + +ex:ClassC-property-top + sh:path ex:property-top ; + . + +ex:ClassD + sh:property ex:ClassD-property-top ; + . + +ex:ClassD-property-top + sh:path ex:property-top ; + . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result + [ + a shir:PropertyShapeComponentBroadenedError-path ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) of ancestor class (rdfs:seeAlso) that has sh:path to superproperty of ancestor class's property shape (sh:sourceShape)." ; + sh:resultPath ex:property-sub-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property-sub-top ; + sh:value ex:ClassC-property-top ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-path ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) of ancestor class (rdfs:seeAlso) that has sh:path to superproperty of ancestor class's property shape (sh:sourceShape)." ; + sh:resultPath ex:property-sub-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property-sub-top ; + sh:value ex:ClassD-property-top ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-path ; + rdfs:seeAlso ex:ClassB ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) of ancestor class (rdfs:seeAlso) that has sh:path to superproperty of ancestor class's property shape (sh:sourceShape)." ; + sh:resultPath ex:property-sub-sub-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassB-property-sub-sub-top ; + sh:value ex:ClassC-property-top ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:property-sub-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property-sub-top ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassD ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:property-sub-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassA-property-sub-top ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassB ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:property-sub-sub-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:ClassB-property-sub-sub-top ; + ] + ; + . + diff --git a/tests/XFAIL_path_ontology.ttl b/tests/XFAIL_path_ontology.ttl new file mode 100644 index 0000000..e42da85 --- /dev/null +++ b/tests/XFAIL_path_ontology.ttl @@ -0,0 +1,116 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property ex:ClassA-property-sub-top ; + . + +ex:ClassA-property-sub-top + sh:path ex:property-sub-top ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property ex:ClassB-property-sub-sub-top ; + . + +ex:ClassB-property-sub-sub-top + sh:path ex:property-sub-sub-top ; + . + +ex:ClassC-property-top + sh:path ex:property-top ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property ex:ClassC-property-top ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property-sub-top ; + sh:sourceShape ex:ClassA-property-sub-top ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-path ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property-sub-top ; + sh:sourceShape ex:ClassA-property-sub-top ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-path ; + rdfs:seeAlso ex:ClassB ; + sh:resultPath ex:property-sub-sub-top ; + sh:sourceShape ex:ClassB-property-sub-sub-top ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassB ; + sh:resultPath ex:property-sub-sub-top ; + sh:sourceShape ex:ClassB-property-sub-sub-top ; + ] + ; + . + +ex:ClassD + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property ex:ClassD-property-top ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeComponentBroadenedError-path ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property-sub-top ; + sh:sourceShape ex:ClassA-property-sub-top ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:property-sub-top ; + sh:sourceShape ex:ClassA-property-sub-top ; + ] + ; + . + +ex:ClassD-property-top + sh:path ex:property-top ; + . + +ex:property-top + a owl:DatatypeProperty ; + . + +ex:property-sub-top + a owl:DatatypeProperty ; + rdfs:subPropertyOf ex:property-top ; + . + +ex:property-sub-sub-top + a owl:DatatypeProperty ; + rdfs:subPropertyOf ex:property-sub-top ; + . + diff --git a/tests/XFAIL_subprop_inheritance.ttl b/tests/XFAIL_subprop_inheritance.ttl new file mode 100644 index 0000000..a4fd825 --- /dev/null +++ b/tests/XFAIL_subprop_inheritance.ttl @@ -0,0 +1,229 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:ClassA + sh:property ex:propertyShape-DatatypeProperty-narrowest ; + . + +ex:ClassB + sh:property ex:propertyShape-DatatypeProperty-middle ; + . + +ex:ClassC + sh:property ex:propertyShape-DatatypeProperty-broadest ; + . + +ex:ClassD + sh:property ex:propertyShape-ObjectProperty-narrowest ; + . + +ex:ClassE + sh:property ex:propertyShape-ObjectProperty-middle ; + . + +ex:ClassF + sh:property ex:propertyShape-ObjectProperty-broadest ; + . + +ex:propertyShape-DatatypeProperty-broadest + a sh:PropertyShape ; + sh:path ex:d-property-sub-top ; + . + +ex:propertyShape-DatatypeProperty-middle + a sh:PropertyShape ; + sh:datatype xsd:integer ; + sh:maxCount "4"^^xsd:integer ; + sh:minCount "1"^^xsd:integer ; + sh:path ex:d-property-sub-sub-top ; + . + +ex:propertyShape-DatatypeProperty-narrowest + a sh:PropertyShape ; + sh:datatype xsd:integer ; + sh:maxCount "3"^^xsd:integer ; + sh:minCount "2"^^xsd:integer ; + sh:path ex:d-property-top ; + . + +ex:propertyShape-ObjectProperty-broadest + a sh:PropertyShape ; + sh:path ex:o-property-sub-top ; + . + +ex:propertyShape-ObjectProperty-middle + a sh:PropertyShape ; + sh:class ex:ClassX ; + sh:maxCount "4"^^xsd:integer ; + sh:minCount "1"^^xsd:integer ; + sh:path ex:o-property-sub-sub-top ; + . + +ex:propertyShape-ObjectProperty-narrowest + a sh:PropertyShape ; + sh:class ex:ClassY ; + sh:maxCount "3"^^xsd:integer ; + sh:minCount "2"^^xsd:integer ; + sh:path ex:o-property-top ; + . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result + [ + a shir:PropertyShapeComponentBroadenedError-class ; + rdfs:seeAlso ex:ClassD ; + sh:focusNode ex:ClassE ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) corresponding with an ancestor class's (rdfs:seeAlso) property shape (sh:sourceShape). However, the sh:class references on the two property shapes are inverted - the subclass shape's sh:class is a superclass of the ancestor class property shape's sh:class." ; + sh:resultPath ex:o-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-middle ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-maxCount ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassB ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) has a lower sh:minCount." ; + sh:resultPath ex:d-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-DatatypeProperty-narrowest ; + sh:value ex:propertyShape-DatatypeProperty-middle ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-maxCount ; + rdfs:seeAlso ex:ClassD ; + sh:focusNode ex:ClassE ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) has a lower sh:minCount." ; + sh:resultPath ex:o-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-middle ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-minCount ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassB ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) has a lower sh:minCount." ; + sh:resultPath ex:d-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-DatatypeProperty-narrowest ; + sh:value ex:propertyShape-DatatypeProperty-middle ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-minCount ; + rdfs:seeAlso ex:ClassD ; + sh:focusNode ex:ClassE ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) has a lower sh:minCount." ; + sh:resultPath ex:o-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-middle ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-path ; + rdfs:seeAlso ex:ClassB ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) of ancestor class (rdfs:seeAlso) that has sh:path to superproperty of ancestor class's property shape (sh:sourceShape)." ; + sh:resultPath ex:d-property-sub-sub-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-DatatypeProperty-middle ; + sh:value ex:propertyShape-DatatypeProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-path ; + rdfs:seeAlso ex:ClassE ; + sh:focusNode ex:ClassF ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) of ancestor class (rdfs:seeAlso) that has sh:path to superproperty of ancestor class's property shape (sh:sourceShape)." ; + sh:resultPath ex:o-property-sub-sub-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-ObjectProperty-middle ; + sh:value ex:propertyShape-ObjectProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-class ; + rdfs:seeAlso ex:ClassD ; + sh:focusNode ex:ClassF ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) corresponding with an ancestor class's (rdfs:seeAlso) property shape (sh:sourceShape). But, the subclass's property shape is missing its sh:class." ; + sh:resultPath ex:o-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-datatype ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its sh:datatype." ; + sh:resultPath ex:d-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-DatatypeProperty-narrowest ; + sh:value ex:propertyShape-DatatypeProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-maxCount ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its maxCount." ; + sh:resultPath ex:d-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-DatatypeProperty-narrowest ; + sh:value ex:propertyShape-DatatypeProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-maxCount ; + rdfs:seeAlso ex:ClassD ; + sh:focusNode ex:ClassF ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor property shape (sh:sourceShape) dropped its maxCount." ; + sh:resultPath ex:o-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-minCount ; + rdfs:seeAlso ex:ClassA ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) dropped its sh:minCount." ; + sh:resultPath ex:d-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-DatatypeProperty-narrowest ; + sh:value ex:propertyShape-DatatypeProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-minCount ; + rdfs:seeAlso ex:ClassD ; + sh:focusNode ex:ClassF ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) dropped its sh:minCount." ; + sh:resultPath ex:o-property-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-broadest ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassB ; + sh:focusNode ex:ClassC ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:d-property-sub-sub-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-DatatypeProperty-middle ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassE ; + sh:focusNode ex:ClassF ; + sh:resultMessage "Subclass (sh:focusNode) is missing property shape (sh:sourceShape) from superclass, according to property in source's sh:path (sh:resultPath)." ; + sh:resultPath ex:o-property-sub-sub-top ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:propertyShape-ObjectProperty-middle ; + ] + ; + . + diff --git a/tests/XFAIL_subprop_ontology.ttl b/tests/XFAIL_subprop_ontology.ttl new file mode 100644 index 0000000..038418c --- /dev/null +++ b/tests/XFAIL_subprop_ontology.ttl @@ -0,0 +1,258 @@ +# baseURI: http://example.org/ontology/example + +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + + + a owl:Ontology ; + rdfs:comment "Class hierarchies go, in increasing depth order, A-B-C and D-E-F. Their property shapes have their relative specificity declared in names, except the sh:path follows the applicable-class order in order to trigger subPropertyOf-based expansion detection." ; + . + +ex:ClassA + a + owl:Class , + sh:NodeShape + ; + sh:property ex:propertyShape-DatatypeProperty-narrowest ; + . + +ex:ClassB + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassA ; + sh:property ex:propertyShape-DatatypeProperty-middle ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeComponentBroadenedError-maxCount ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:d-property-top ; + sh:sourceShape ex:propertyShape-DatatypeProperty-narrowest ; + sh:value ex:propertyShape-DatatypeProperty-middle + ] , + [ + a shir:PropertyShapeComponentBroadenedError-minCount ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:d-property-top ; + sh:sourceShape ex:propertyShape-DatatypeProperty-narrowest ; + sh:value ex:propertyShape-DatatypeProperty-middle ; + ] + ; + . + +ex:ClassC + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassB ; + sh:property ex:propertyShape-DatatypeProperty-broadest ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeComponentBroadenedError-path ; + rdfs:seeAlso ex:ClassB ; + sh:resultPath ex:d-property-sub-sub-top ; + sh:sourceShape ex:propertyShape-DatatypeProperty-middle ; + sh:value ex:propertyShape-DatatypeProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-datatype ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:d-property-top ; + sh:sourceShape ex:propertyShape-DatatypeProperty-narrowest ; + sh:value ex:propertyShape-DatatypeProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-maxCount ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:d-property-top ; + sh:sourceShape ex:propertyShape-DatatypeProperty-narrowest ; + sh:value ex:propertyShape-DatatypeProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-minCount ; + rdfs:seeAlso ex:ClassA ; + sh:resultPath ex:d-property-top ; + sh:sourceShape ex:propertyShape-DatatypeProperty-narrowest ; + sh:value ex:propertyShape-DatatypeProperty-broadest ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassB ; + sh:resultPath ex:d-property-sub-sub-top ; + sh:sourceShape ex:propertyShape-DatatypeProperty-middle ; + ] + ; + . + +ex:ClassD + a + owl:Class , + sh:NodeShape + ; + sh:property ex:propertyShape-ObjectProperty-narrowest ; + . + +ex:ClassE + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassD ; + sh:property ex:propertyShape-ObjectProperty-middle ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeComponentBroadenedError-class ; + rdfs:seeAlso ex:ClassD ; + sh:resultPath ex:o-property-top ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-middle ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-maxCount ; + rdfs:seeAlso ex:ClassD ; + sh:resultPath ex:o-property-top ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-middle ; + ] , + [ + a shir:PropertyShapeComponentBroadenedError-minCount ; + rdfs:seeAlso ex:ClassD ; + sh:resultPath ex:o-property-top ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-middle ; + ] + ; + . + +ex:ClassF + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:ClassE ; + sh:property ex:propertyShape-ObjectProperty-broadest ; + shir:shouldTriggerBroadeningError + [ + a shir:PropertyShapeComponentBroadenedError-path ; + rdfs:seeAlso ex:ClassE ; + sh:resultPath ex:o-property-sub-sub-top ; + sh:sourceShape ex:propertyShape-ObjectProperty-middle ; + sh:value ex:propertyShape-ObjectProperty-broadest + ] , + [ + a shir:PropertyShapeComponentDroppedError-class ; + rdfs:seeAlso ex:ClassD ; + sh:resultPath ex:o-property-top ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-maxCount ; + rdfs:seeAlso ex:ClassD ; + sh:resultPath ex:o-property-top ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-broadest ; + ] , + [ + a shir:PropertyShapeComponentDroppedError-minCount ; + rdfs:seeAlso ex:ClassD ; + sh:resultPath ex:o-property-top ; + sh:sourceShape ex:propertyShape-ObjectProperty-narrowest ; + sh:value ex:propertyShape-ObjectProperty-broadest ; + ] , + [ + a shir:PropertyShapeDroppedError ; + rdfs:seeAlso ex:ClassE ; + sh:resultPath ex:o-property-sub-sub-top ; + sh:sourceShape ex:propertyShape-ObjectProperty-middle + ] + ; + . + +ex:ClassX + a owl:Class ; + . + +ex:ClassY + a owl:Class ; + rdfs:subClassOf ex:ClassX ; + . + +ex:propertyShape-DatatypeProperty-broadest + a sh:PropertyShape ; + sh:path ex:d-property-sub-top ; + . + +ex:propertyShape-DatatypeProperty-middle + a sh:PropertyShape ; + sh:path ex:d-property-sub-sub-top ; + sh:minCount 1; + sh:maxCount 4; + sh:datatype xsd:integer ; + . + +ex:propertyShape-DatatypeProperty-narrowest + a sh:PropertyShape ; + sh:path ex:d-property-top ; + sh:minCount 2; + sh:maxCount 3; + sh:datatype xsd:integer ; + . + +ex:propertyShape-ObjectProperty-broadest + a sh:PropertyShape ; + sh:path ex:o-property-sub-top ; + . + +ex:propertyShape-ObjectProperty-middle + a sh:PropertyShape ; + sh:path ex:o-property-sub-sub-top ; + sh:minCount 1; + sh:maxCount 4; + sh:class ex:ClassX ; + . + +ex:propertyShape-ObjectProperty-narrowest + a sh:PropertyShape ; + sh:path ex:o-property-top ; + sh:minCount 2; + sh:maxCount 3; + sh:class ex:ClassY ; + . + +ex:d-property-top + a owl:DatatypeProperty ; + . + +ex:d-property-sub-top + a owl:DatatypeProperty ; + rdfs:subPropertyOf ex:d-property-top ; + . + +ex:d-property-sub-sub-top + a owl:DatatypeProperty ; + rdfs:subPropertyOf ex:d-property-sub-top ; + . + +ex:o-property-top + a owl:ObjectProperty ; + . + +ex:o-property-sub-top + a owl:ObjectProperty ; + rdfs:subPropertyOf ex:o-property-top ; + . + +ex:o-property-sub-sub-top + a owl:ObjectProperty ; + rdfs:subPropertyOf ex:o-property-sub-top ; + . + diff --git a/tests/ex-triangle-1-1.ttl b/tests/ex-triangle-1-1.ttl new file mode 100644 index 0000000..8cd1c4b --- /dev/null +++ b/tests/ex-triangle-1-1.ttl @@ -0,0 +1,17 @@ +@prefix ex: . +@prefix owl: . +@prefix rdfs: . + +ex:Triangle + a owl:Class ; + . + +ex:Point + a owl:Class ; + . + +ex:hasPoint + a owl:ObjectProperty ; + rdfs:domain ex:Triangle ; + rdfs:range ex:Point ; + . diff --git a/tests/ex-triangle-1-2.ttl b/tests/ex-triangle-1-2.ttl new file mode 100644 index 0000000..4cdc837 --- /dev/null +++ b/tests/ex-triangle-1-2.ttl @@ -0,0 +1,16 @@ +@prefix ex: . +@prefix sh: . + +ex:Triangle + a sh:NodeShape ; + sh:property ex:PropertyShape-1 ; + sh:targetClass ex:Triangle ; + . + +ex:PropertyShape-1 + a sh:PropertyShape ; + sh:path ex:hasPoint ; + sh:class ex:Point ; + sh:minCount 3 ; + sh:maxCount 3 ; + . diff --git a/tests/ex-triangle-2.ttl b/tests/ex-triangle-2.ttl new file mode 100644 index 0000000..2e3ba29 --- /dev/null +++ b/tests/ex-triangle-2.ttl @@ -0,0 +1,22 @@ +@prefix ex: . +@prefix owl: . +@prefix rdfs: . +@prefix sh: . + +ex:Triangle-but-1-dimensional + a + sh:NodeShape , + owl:Class + ; + rdfs:subClassOf ex:Triangle ; + sh:property ex:PropertyShape-2 ; + sh:targetClass ex:Triangle-but-1-dimensional ; + . + +ex:PropertyShape-2 + a sh:PropertyShape ; + sh:path ex:hasPoint ; + sh:class ex:Point ; + sh:minCount 2 ; + sh:maxCount 2 ; + . diff --git a/tests/ex-triangle-inheritance.ttl b/tests/ex-triangle-inheritance.ttl new file mode 100644 index 0000000..8bbb545 --- /dev/null +++ b/tests/ex-triangle-inheritance.ttl @@ -0,0 +1,47 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shir: . +@prefix xsd: . + +ex:PropertyShape-1 + a sh:PropertyShape ; + sh:class ex:Point ; + sh:maxCount "3"^^xsd:integer ; + sh:minCount "3"^^xsd:integer ; + sh:path ex:hasPoint ; + . + +ex:PropertyShape-2 + a sh:PropertyShape ; + sh:class ex:Point ; + sh:maxCount "2"^^xsd:integer ; + sh:minCount "2"^^xsd:integer ; + sh:path ex:hasPoint ; + . + +ex:Triangle + sh:property ex:PropertyShape-1 ; + . + +ex:Triangle-but-1-dimensional + sh:property ex:PropertyShape-2 ; + . + +[] + a shir:InheritanceValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result [ + a shir:PropertyShapeComponentBroadenedError-minCount ; + rdfs:seeAlso ex:Triangle ; + sh:focusNode ex:Triangle-but-1-dimensional ; + sh:resultMessage "Subclass (sh:focusNode) has property shape (sh:value) from ancestor class (rdfs:seeAlso), but according to ancestor's property shape (sh:sourceShape) has a lower sh:minCount." ; + sh:resultPath ex:hasPoint ; + sh:resultSeverity sh:Violation ; + sh:sourceShape ex:PropertyShape-1 ; + sh:value ex:PropertyShape-2 ; + ] ; + . + diff --git a/tests/ex-triangle.ttl b/tests/ex-triangle.ttl new file mode 100644 index 0000000..9107bf8 --- /dev/null +++ b/tests/ex-triangle.ttl @@ -0,0 +1,52 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +ex:Point + a owl:Class ; + . + +ex:PropertyShape-1 + a sh:PropertyShape ; + sh:class ex:Point ; + sh:maxCount "3"^^xsd:integer ; + sh:minCount "3"^^xsd:integer ; + sh:path ex:hasPoint ; + . + +ex:PropertyShape-2 + a sh:PropertyShape ; + sh:class ex:Point ; + sh:maxCount "2"^^xsd:integer ; + sh:minCount "2"^^xsd:integer ; + sh:path ex:hasPoint ; + . + +ex:Triangle + a + owl:Class , + sh:NodeShape + ; + sh:property ex:PropertyShape-1 ; + sh:targetClass ex:Triangle ; + . + +ex:Triangle-but-1-dimensional + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf ex:Triangle ; + sh:property ex:PropertyShape-2 ; + sh:targetClass ex:Triangle-but-1-dimensional ; + . + +ex:hasPoint + a owl:ObjectProperty ; + rdfs:domain ex:Triangle ; + rdfs:range ex:Point ; + . + diff --git a/tests/ex_ttl.py b/tests/ex_ttl.py new file mode 100644 index 0000000..ea4552f --- /dev/null +++ b/tests/ex_ttl.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +""" +This is a single-purpose script to compile a set of Turtle ontology files into one. +""" + +import sys + +import rdflib + +g = rdflib.Graph() +for filename in sys.argv[2:]: + g.parse(filename, format="turtle") +g.serialize(sys.argv[1], format="turtle") diff --git a/tests/kb-test-1.ttl b/tests/kb-test-1.ttl new file mode 100644 index 0000000..9b8084b --- /dev/null +++ b/tests/kb-test-1.ttl @@ -0,0 +1,6 @@ +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms true . + diff --git a/tests/kb-test-2.ttl b/tests/kb-test-2.ttl new file mode 100644 index 0000000..53db4fa --- /dev/null +++ b/tests/kb-test-2.ttl @@ -0,0 +1,14 @@ +@prefix ex: . +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms false ; + sh:result [ a sh:ValidationResult ; + sh:focusNode ; + sh:resultMessage "Less than 3 values on kb:triangle-2->ex:hasPoint" ; + sh:resultPath ex:hasPoint ; + sh:resultSeverity sh:Violation ; + sh:sourceConstraintComponent sh:MinCountConstraintComponent ; + sh:sourceShape ex:PropertyShape-1 ] . + diff --git a/tests/kb-test-3.ttl b/tests/kb-test-3.ttl new file mode 100644 index 0000000..9b8084b --- /dev/null +++ b/tests/kb-test-3.ttl @@ -0,0 +1,6 @@ +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms true . + diff --git a/tests/kb-test-4.ttl b/tests/kb-test-4.ttl new file mode 100644 index 0000000..9b8084b --- /dev/null +++ b/tests/kb-test-4.ttl @@ -0,0 +1,6 @@ +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms true . + diff --git a/tests/kb-test-5.ttl b/tests/kb-test-5.ttl new file mode 100644 index 0000000..dbeb252 --- /dev/null +++ b/tests/kb-test-5.ttl @@ -0,0 +1,14 @@ +@prefix ex: . +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms false ; + sh:result [ a sh:ValidationResult ; + sh:focusNode ; + sh:resultMessage "Less than 3 values on kb:triangle-3->ex:hasPoint" ; + sh:resultPath ex:hasPoint ; + sh:resultSeverity sh:Violation ; + sh:sourceConstraintComponent sh:MinCountConstraintComponent ; + sh:sourceShape ex:PropertyShape-1 ] . + diff --git a/tests/kb-test-6.ttl b/tests/kb-test-6.ttl new file mode 100644 index 0000000..9b8084b --- /dev/null +++ b/tests/kb-test-6.ttl @@ -0,0 +1,6 @@ +@prefix sh: . +@prefix xsd: . + +[] a sh:ValidationReport ; + sh:conforms true . + diff --git a/tests/kb-triangle-1.ttl b/tests/kb-triangle-1.ttl new file mode 100644 index 0000000..75ab39d --- /dev/null +++ b/tests/kb-triangle-1.ttl @@ -0,0 +1,13 @@ +@prefix ex: . +@prefix kb: . + +kb:point-1 a ex:Point . +kb:point-2 a ex:Point . +kb:point-3 a ex:Point . + +kb:triangle-1 + a ex:Triangle ; + ex:hasPoint kb:point-1 ; + ex:hasPoint kb:point-2 ; + ex:hasPoint kb:point-3 ; + . diff --git a/tests/kb-triangle-2.ttl b/tests/kb-triangle-2.ttl new file mode 100644 index 0000000..404fe0c --- /dev/null +++ b/tests/kb-triangle-2.ttl @@ -0,0 +1,11 @@ +@prefix ex: . +@prefix kb: . + +kb:point-4 a ex:Point . +kb:point-5 a ex:Point . + +kb:triangle-2 + a ex:Triangle ; + ex:hasPoint kb:point-4 ; + ex:hasPoint kb:point-5 ; + . diff --git a/tests/kb-triangle-3-super.ttl b/tests/kb-triangle-3-super.ttl new file mode 100644 index 0000000..35938df --- /dev/null +++ b/tests/kb-triangle-3-super.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix kb: . + +kb:point-6 a ex:Point . +kb:point-7 a ex:Point . + +kb:triangle-3 + a ex:Triangle ; + a ex:Triangle-but-1-dimensional ; + ex:hasPoint kb:point-6 ; + ex:hasPoint kb:point-7 ; + . diff --git a/tests/kb-triangle-3.ttl b/tests/kb-triangle-3.ttl new file mode 100644 index 0000000..ec6108b --- /dev/null +++ b/tests/kb-triangle-3.ttl @@ -0,0 +1,11 @@ +@prefix ex: . +@prefix kb: . + +kb:point-6 a ex:Point . +kb:point-7 a ex:Point . + +kb:triangle-3 + a ex:Triangle-but-1-dimensional ; + ex:hasPoint kb:point-6 ; + ex:hasPoint kb:point-7 ; + . diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..e079f8a --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1 @@ +pytest diff --git a/tests/test_all.py b/tests/test_all.py new file mode 100644 index 0000000..fc52ca1 --- /dev/null +++ b/tests/test_all.py @@ -0,0 +1,282 @@ +#!/usr/bin/python + +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +""" +This script was written to run unit tests in the pytest framework. +""" + +import glob +import logging +import os +import typing + +import pytest +import rdflib.plugins.sparql + +_logger = logging.getLogger(os.path.basename(__file__)) + +NS_EX = rdflib.Namespace("http://example.org/ontology/example/") +NS_SH = rdflib.SH +NS_SHIR = rdflib.Namespace("http://example.org/ontology/shacl-inheritance-review/") + +def load_and_check_graph( + basename : str, + required_conformance : bool, + conformance_mismatch_expectation : typing.Optional[str] = None +) -> rdflib.Graph: + graph = rdflib.Graph() + graph_filepath = os.path.join(os.path.dirname(__file__), basename) + graph.load(graph_filepath, format="turtle") + conforms = None + for triple in graph.triples((None, NS_SH.conforms, None)): + assert conforms is None, "Found second result." + conforms = triple[2].toPython() + + if conformance_mismatch_expectation is None: + assert conforms == required_conformance + else: + try: + assert conforms == required_conformance + raise ValueError("XPASS - Was expecting failure.") + except: + pytest.xfail(conformance_mismatch_expectation) + return graph + +def load_ontology_graph( + basename : str +) -> rdflib.Graph: + graph = rdflib.Graph() + graph_filepath = os.path.join(os.path.dirname(__file__), basename) + graph.load(graph_filepath, format="turtle") + return graph + +def test_coverage(): + # Ground truth: + # * There is an expected set of IRIs of error classes emitted by the reports. + expected = { + str(NS_SHIR["PropertyShapeComponentBroadenedError-class"]), + str(NS_SHIR["PropertyShapeComponentBroadenedError-datatype"]), + str(NS_SHIR["PropertyShapeComponentBroadenedError-maxCount"]), + str(NS_SHIR["PropertyShapeComponentBroadenedError-minCount"]), + str(NS_SHIR["PropertyShapeComponentBroadenedError-path"]), + str(NS_SHIR["PropertyShapeComponentDroppedError-class"]), + str(NS_SHIR["PropertyShapeComponentDroppedError-datatype"]), + str(NS_SHIR["PropertyShapeComponentDroppedError-maxCount"]), + str(NS_SHIR["PropertyShapeComponentDroppedError-minCount"]), + str(NS_SHIR["PropertyShapeDroppedError"]) + } + computed = set() + + graph = rdflib.Graph() + srcdir = os.path.dirname(__file__) + top_srcdir = os.path.dirname(srcdir) + + # Load XFAIL reports. + for inheritance_ttl in sorted(glob.glob(os.path.join(srcdir, "XFAIL_*_inheritance.ttl"))): + _logger.debug("inheritance_ttl = %r.", inheritance_ttl) + basename = os.path.basename(inheritance_ttl) + tmp_graph = load_and_check_graph(basename, False) + graph += tmp_graph + + query = rdflib.plugins.sparql.prepareQuery("""\ +PREFIX sh: +PREFIX shir: + +SELECT ?nClass +WHERE { + ?nReport + a shir:InheritanceValidationReport ; + sh:result/a ?nClass ; + . +} +""") + for result in graph.query(query): + n_class = result[0] + computed.add(str(n_class)) + + try: + assert expected == computed + except: + if computed - expected != set(): + raise + if expected - computed == {str(NS_SHIR["PropertyShapeComponentBroadenedError-datatype"])}: + pytest.xfail("At this time, the broadened-on-datatype test has not been specified.") + raise ValueError("XPASS - Since original writing, the broadened-on-datatype test has been specified and should be reviewed for the test_coverage function.") + +def _test_inheritance_xfail_from_inlined_ground_truth( + ontology_basename : str, + inheritance_basename : str +): + ontology_graph = load_ontology_graph(ontology_basename) + inheritance_graph = load_and_check_graph(inheritance_basename, False) + expected = set() + computed = set() + + expected_query = rdflib.plugins.sparql.prepareQuery("""\ +PREFIX sh: +PREFIX shir: + +SELECT ?nClassNodeShape ?nClassPropertyShape ?nClassPropertyShapePath ?nSuperclassNodeShape ?nSuperclassPropertyShape ?nSuperclassPropertyShapePath ?nErrorClass +WHERE { + ?nClassNodeShape + a sh:NodeShape ; + shir:shouldTriggerBroadeningError ?nExpectedError ; + . + + ?nExpectedError + a ?nErrorClass ; + sh:resultPath ?nSuperclassPropertyShapePath ; + sh:sourceShape ?nSuperclassPropertyShape ; + rdfs:seeAlso ?nSuperclassNodeShape ; + . + OPTIONAL { + ?nExpectedError + sh:value ?nClassPropertyShape ; + . + ?nClassPropertyShape + sh:path ?nClassPropertyShapePath + } +} +""") + for result in ontology_graph.query(expected_query): + ( + n_class_node_shape, + n_class_property_shape, + n_class_property_shape_path, + n_superclass_node_shape, + n_superclass_property_shape, + n_superclass_property_shape_path, + n_error_class + ) = result + expected.add(( + n_error_class.toPython(), + n_class_node_shape.toPython(), + n_superclass_node_shape.toPython(), + n_superclass_property_shape_path.toPython() + )) + + computed_query = rdflib.plugins.sparql.prepareQuery("""\ +PREFIX sh: +PREFIX shir: + +SELECT ?nClassNodeShape ?nSuperclassNodeShape ?nSuperclassPropertyShapePath ?nErrorClass +WHERE { + ?nReport + a shir:InheritanceValidationReport ; + sh:result ?nResult ; + . + + ?nResult + a ?nErrorClass ; + rdfs:seeAlso ?nSuperclassNodeShape ; + sh:focusNode ?nClassNodeShape ; + sh:resultPath ?nSuperclassPropertyShapePath ; + sh:sourceShape ?nSuperclassPropertyShape ; + . + + ?nSuperclassPropertyShape + sh:path ?nSuperclassPropertyShapePath ; + . +} +""") + for result in inheritance_graph.query(computed_query): + ( + n_class_node_shape, + n_superclass_node_shape, + n_superclass_property_shape_path, + n_error_class + ) = result + computed.add(( + n_error_class.toPython(), + n_class_node_shape.toPython(), + n_superclass_node_shape.toPython(), + n_superclass_property_shape_path.toPython() + )) + assert expected == computed + +def test_kb_test_1(): + g = load_and_check_graph("kb-test-1.ttl", True) + assert isinstance(g, rdflib.Graph) + +def test_kb_test_2(): + g = load_and_check_graph("kb-test-2.ttl", False) + assert isinstance(g, rdflib.Graph) + +def test_kb_test_3(): + g = load_and_check_graph("kb-test-3.ttl", True) + assert isinstance(g, rdflib.Graph) + +def test_kb_test_4(): + g = load_and_check_graph("kb-test-4.ttl", False, "When this was written, pyshacl was known to disagree with test developer's subclass--shape expectations.") + assert isinstance(g, rdflib.Graph) + +def test_kb_test_5(): + g = load_and_check_graph("kb-test-5.ttl", False) + assert isinstance(g, rdflib.Graph) + +def test_kb_test_6(): + g = load_and_check_graph("kb-test-6.ttl", False, "When this was written, pyshacl was known to disagree with test developer's subclass--shape expectations.") + assert isinstance(g, rdflib.Graph) + +def test_pass_PropertyShape(): + g = load_and_check_graph("PASS_PropertyShape_inheritance.ttl", True) + assert isinstance(g, rdflib.Graph) + +def test_pass_class(): + g = load_and_check_graph("PASS_class_inheritance.ttl", True) + assert isinstance(g, rdflib.Graph) + +def test_pass_datatype(): + g = load_and_check_graph("PASS_datatype_inheritance.ttl", True) + assert isinstance(g, rdflib.Graph) + +def test_pass_maxCount(): + g = load_and_check_graph("PASS_maxCount_inheritance.ttl", True) + assert isinstance(g, rdflib.Graph) + +def test_pass_minCount(): + g = load_and_check_graph("PASS_minCount_inheritance.ttl", True) + assert isinstance(g, rdflib.Graph) + +def test_pass_path(): + g = load_and_check_graph("PASS_path_inheritance.ttl", True) + assert isinstance(g, rdflib.Graph) + +def test_pass_subprop(): + g = load_and_check_graph("PASS_path_inheritance.ttl", True) + assert isinstance(g, rdflib.Graph) + +def test_xfail_PropertyShape_inheritance(): + _test_inheritance_xfail_from_inlined_ground_truth("XFAIL_PropertyShape_ontology.ttl", "XFAIL_PropertyShape_inheritance.ttl") + +def test_xfail_class_inheritance(): + _test_inheritance_xfail_from_inlined_ground_truth("XFAIL_class_ontology.ttl", "XFAIL_class_inheritance.ttl") + +def test_xfail_datatype_inheritance(): + _test_inheritance_xfail_from_inlined_ground_truth("XFAIL_datatype_ontology.ttl", "XFAIL_datatype_inheritance.ttl") + +def test_xfail_maxCount_inheritance(): + _test_inheritance_xfail_from_inlined_ground_truth("XFAIL_maxCount_ontology.ttl", "XFAIL_maxCount_inheritance.ttl") + +def test_xfail_minCount_inheritance(): + _test_inheritance_xfail_from_inlined_ground_truth("XFAIL_minCount_ontology.ttl", "XFAIL_minCount_inheritance.ttl") + +def test_xfail_path_inheritance(): + _test_inheritance_xfail_from_inlined_ground_truth("XFAIL_path_ontology.ttl", "XFAIL_path_inheritance.ttl") + +def test_xfail_subprop_inheritance(): + _test_inheritance_xfail_from_inlined_ground_truth("XFAIL_subprop_ontology.ttl", "XFAIL_subprop_inheritance.ttl") + +def test_ex_triangle_inheritance(): + g = load_and_check_graph("ex-triangle-inheritance.ttl", False) + assert isinstance(g, rdflib.Graph)