Skip to content

Commit

Permalink
Merge pull request #307 from emmo-repo/cwa/fix-306-ontodoc-rdflib-import
Browse files Browse the repository at this point in the history
Fix ontoconvert rdflib import & add simple run tests for tools.
  • Loading branch information
CasperWA authored Dec 2, 2021
2 parents dce92c0 + 5cb20d2 commit 4dc1e97
Show file tree
Hide file tree
Showing 17 changed files with 207 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci_cd_updated_master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
- name: Release check
run: |
COMMIT_MSG="$(gh api /repos/${{ github.repository}}/commits/${{ env.DEFAULT_REPO_BRANCH }} --jq '.commit.message')"
if [[ "${COMMIT_MSG}" =~ ^Release\ v.*\ -\ Changelog$ ]]; then
if [[ "${COMMIT_MSG}" =~ ^Release\ v.*$ ]]; then
echo "In a release - do not run this job !"
echo "RELEASE_RUN=true" >> $GITHUB_ENV
else
Expand Down
40 changes: 40 additions & 0 deletions docs/developers/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Testing and tooling

## Unit testing

The [PyTest](https://pytest.org) framework is used for testing the EMMOntoPy package.
It is a unit testing framework with a plugin system, sporting an extensive plugin library as well as a sound fixture injection system.

To run the tests locally install the package with the `dev` extra (see the [developer's setup guide](setup.md)) and run:

```console
$ pytest
=== test session starts ===
...
```

To understand what options you have, run `pytest --help`.

## Tools

Several tools are used to maintain the package, keeping it secure, readable, and easing maintenance.

### Mypy

[Mypy](http://mypy-lang.org/) is a static type checker for Python.

**Documentation**: [mypy.readthedocs.io](https://mypy.readthedocs.io/)

The signs of this tool will be found in the code especially through the `typing.TYPE_CHECKING` boolean variable, which will be used in the current way:

```python
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import List
```

Since `TYPE_CHECKING` is `False` at runtime, the `if`-block will not be run as part of running the script or module or if importing the module.
However, when Mypy runs to check the static typing, it forcefully runs these blocks, considering `TYPE_CHECKING` to be `True` (see the [`typing.TYPE_CHECKING` section](https://mypy.readthedocs.io/en/stable/runtime_troubles.html#typing-type-checking) in the Mypy documentation).

This means the imports in the `if`-block are meant to *only* be used for static typing, helping developers to understand the intention of the code as well as to check the invoked methods make sense (through Mypy).
26 changes: 16 additions & 10 deletions emmopy/emmocheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,13 +411,8 @@ def test_namespace(self):
exceptions.update(self.get_config("test_namespace.exceptions", ()))

def checker(onto, ignore_namespace):
if (
list(
filter(
onto.base_iri.strip("#").endswith, self.ignore_namespace
)
)
!= []
if list(
filter(onto.base_iri.strip("#").endswith, self.ignore_namespace)
):
print(f"Skipping namespace: {onto.base_iri}")
return
Expand Down Expand Up @@ -463,8 +458,19 @@ def checker(onto, ignore_namespace):
checker(self.onto, self.ignore_namespace)


def main(): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Run all checks on ontology `iri`. Default is 'http://emmo.info/emmo'."""
def main(
argv: list = None,
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Run all checks on ontology `iri`.
Default is 'http://emmo.info/emmo'.
Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.
"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("iri", help="File name or URI to the ontology to test.")
parser.add_argument(
Expand Down Expand Up @@ -597,7 +603,7 @@ def main(): # pylint: disable=too-many-locals,too-many-branches,too-many-statem
help="Stop the test run on the first error or failure.",
)
try:
args = parser.parse_args()
args = parser.parse_args(args=argv)
sys.argv[1:] = args.unittest if args.unittest else []
if args.verbose:
sys.argv.append("-v")
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
35 changes: 35 additions & 0 deletions tests/tools/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Pytest fixtures for the `tools` dir only."""
from typing import TYPE_CHECKING

import pytest

if TYPE_CHECKING:
from types import ModuleType
from typing import Any, Dict


@pytest.fixture
def tool(request: "Dict[str, Any]") -> "ModuleType":
"""Import a tool as a module."""
from copy import deepcopy
import importlib
from pathlib import Path
import sys

original_sys_path = deepcopy(sys.path)
original_tool_path: Path = (
Path(__file__).resolve().parent.parent.parent / "tools" / request.param
)

assert (
original_tool_path.exists()
), f"The requested tool ({request.param}) was not found in {original_tool_path.parent}"
try:
tool_path = original_tool_path.rename(
original_tool_path.with_name(f"{request.param}.py")
)
yield importlib.import_module(f"tools.{request.param}")
finally:
if tool_path and tool_path.exists():
tool_path.rename(tool_path.with_name(request.param))
sys.path = original_sys_path
14 changes: 14 additions & 0 deletions tests/tools/test_emmocheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Test the `emmocheck` tool."""
import pytest


@pytest.mark.parametrize("tool", ["emmocheck"], indirect=True)
def test_run(tool) -> None:
"""Check that running `emmocheck` works."""
from pathlib import Path

test_file = (
Path(__file__).resolve().parent.parent / "testonto" / "models.ttl"
)

tool.main([str(test_file)])
14 changes: 14 additions & 0 deletions tests/tools/test_ontoconvert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Test the `ontoconvert` tool."""
from pathlib import Path

import pytest


@pytest.mark.parametrize("tool", ["ontoconvert"], indirect=True)
def test_run(tool, tmpdir: Path) -> None:
"""Check that running `ontoconvert` works."""
test_file = (
Path(__file__).resolve().parent.parent / "testonto" / "models.ttl"
)

tool.main([str(test_file), str(tmpdir / "test.ttl")])
15 changes: 15 additions & 0 deletions tests/tools/test_ontodoc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Test the `ontodoc` tool."""
from pathlib import Path

import pytest


@pytest.mark.skip("ontodoc is tested in other ways")
@pytest.mark.parametrize("tool", ["ontodoc"], indirect=True)
def test_run(tool, tmpdir: Path) -> None:
"""Check that running `ontodoc` works."""
test_file = (
Path(__file__).resolve().parent.parent / "testonto" / "models.ttl"
)

tool.main([str(test_file), str(tmpdir / "test.md")])
14 changes: 14 additions & 0 deletions tests/tools/test_ontograph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Test the `ontograph` tool."""
from pathlib import Path

import pytest


@pytest.mark.parametrize("tool", ["ontograph"], indirect=True)
def test_run(tool, tmpdir: Path) -> None:
"""Check that running `ontograph` works."""
test_file = (
Path(__file__).resolve().parent.parent / "testonto" / "models.ttl"
)

tool.main([str(test_file), str(tmpdir / "test.png")])
14 changes: 14 additions & 0 deletions tests/tools/test_ontoversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Test the `ontoversion` tool."""
import pytest


@pytest.mark.parametrize("tool", ["ontoversion"], indirect=True)
def test_run(tool) -> None:
"""Check running `ontoversion` works."""
from pathlib import Path

test_file = (
Path(__file__).resolve().parent.parent / "testonto" / "testonto.ttl"
)

tool.main([str(test_file), "--format", "ttl"])
16 changes: 12 additions & 4 deletions tools/ontoconvert
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import argparse
import os
import warnings

from rdflib.util import Graph, guess_format
from rdflib.graph import Graph
from rdflib.util import guess_format

from ontopy.utils import (
convert_imported,
Expand All @@ -17,8 +18,15 @@ from ontopy.utils import (
from ontopy.factpluspluswrapper.factppgraph import FaCTPPGraph


def main():
"""Main run function."""
def main(argv: list = None):
"""Main run function.
Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.
"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("input", help="IRI/file to OWL source.")
parser.add_argument("output", help="Output file name.")
Expand Down Expand Up @@ -85,7 +93,7 @@ def main():
),
)

args = parser.parse_args()
args = parser.parse_args(args=argv)

# Inferred default input and output file formats
if args.input_format:
Expand Down
13 changes: 10 additions & 3 deletions tools/ontodoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ from ontopy.ontodoc import (
import owlready2


def main():
"""Main run function."""
def main(argv: list = None):
"""Main run function.
Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.
"""
parser = argparse.ArgumentParser(
description="Tool for documenting ontologies.",
epilog=(
Expand Down Expand Up @@ -192,7 +199,7 @@ def main():
"debugging)."
),
)
args = parser.parse_args()
args = parser.parse_args(args=argv)

# Append to onto_path
for paths in args.path:
Expand Down
15 changes: 12 additions & 3 deletions tools/ontograph
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@ from ontopy.utils import get_label
import owlready2


def main(): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Main run function."""
def main(
argv: list = None,
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Main run function.
Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.
"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"iri",
Expand Down Expand Up @@ -193,7 +202,7 @@ def main(): # pylint: disable=too-many-locals,too-many-branches,too-many-statem
parser.add_argument(
"--display", "-D", action="store_true", help="Whether to display graph."
)
args = parser.parse_args()
args = parser.parse_args(args=argv)

# Append to onto_path
for paths in args.path:
Expand Down
13 changes: 10 additions & 3 deletions tools/ontoversion
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ def infer_version(iri, version_iri):
return version.lstrip("/").rstrip("/#")


def main():
"""Main run function."""
def main(argv: list = None):
"""Main run function.
Parameters:
argv: List of arguments, similar to `sys.argv[1:]`.
Mainly for testing purposes, since it allows one to invoke the tool
manually / through Python.
"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"iri",
Expand All @@ -37,7 +44,7 @@ def main():
"--format", "-f", default="xml", help='OWL format. Default is "xml".'
)
try:
args = parser.parse_args()
args = parser.parse_args(args=argv)
except SystemExit as exc:
sys.exit(exc.code) # Exit without traceback on invalid arguments

Expand Down

0 comments on commit 4dc1e97

Please sign in to comment.