From 8d470b6dbfab543768a8115563f2dacc52af5b33 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Mon, 30 Aug 2021 18:08:22 +0200 Subject: [PATCH 1/8] Uncomment unusable CLI options Uncommenting `--log-level` and `--debug`. They should be uncommented and used when support for them are available, or eventually removed if it's determined they should not be implemented. --- dic2owl/dic2owl/cli.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/dic2owl/dic2owl/cli.py b/dic2owl/dic2owl/cli.py index 2277c93..b5b77fc 100644 --- a/dic2owl/dic2owl/cli.py +++ b/dic2owl/dic2owl/cli.py @@ -5,13 +5,14 @@ ontology-generation tool for CIF `.dic`-files. """ import argparse -import logging + +# import logging from pathlib import Path -LOGGING_LEVELS = [ - logging.getLevelName(level).lower() for level in range(0, 51, 10) -] +# LOGGING_LEVELS = [ +# logging.getLevelName(level).lower() for level in range(0, 51, 10) +# ] def main(argv: list = None) -> None: @@ -33,18 +34,18 @@ def main(argv: list = None) -> None: help="Show the version and exit.", version=f"dic2owl version {__version__}", ) - parser.add_argument( - "--log-level", - type=str, - help="Set the stdout log-level (verbosity).", - choices=LOGGING_LEVELS, - default="info", - ) - parser.add_argument( - "--debug", - action="store_true", - help="Overrule log-level option, setting it to 'debug'.", - ) + # parser.add_argument( + # "--log-level", + # type=str, + # help="Set the stdout log-level (verbosity).", + # choices=LOGGING_LEVELS, + # default="info", + # ) + # parser.add_argument( + # "--debug", + # action="store_true", + # help="Overrule log-level option, setting it to 'debug'.", + # ) parser.add_argument( "-o", "--output", From 616f0bcb1792998939f4bdf711f306d9dc58ecba Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Mon, 30 Aug 2021 18:21:22 +0200 Subject: [PATCH 2/8] Add unit tests for dic2owl CLI --- .gitignore | 1 + dic2owl/requirements_dev.txt | 2 + tests/dic2owl/conftest.py | 149 ++++++++++++++++ tests/dic2owl/static/cif_core_minimized.dic | 185 ++++++++++++++++++++ tests/dic2owl/static/cif_core_minimized.ttl | 178 +++++++++++++++++++ tests/dic2owl/test_cli.py | 95 ++++++++++ 6 files changed, 610 insertions(+) create mode 100644 tests/dic2owl/conftest.py create mode 100755 tests/dic2owl/static/cif_core_minimized.dic create mode 100644 tests/dic2owl/static/cif_core_minimized.ttl create mode 100644 tests/dic2owl/test_cli.py diff --git a/.gitignore b/.gitignore index bc1ddcd..01483d3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ __pycache__ *.cif !test-cif/**/*.cif +!tests/**/*.dic diff --git a/dic2owl/requirements_dev.txt b/dic2owl/requirements_dev.txt index 84a2750..09b7b96 100644 --- a/dic2owl/requirements_dev.txt +++ b/dic2owl/requirements_dev.txt @@ -2,3 +2,5 @@ bandit~=1.7.0 mypy==0.910 pre-commit~=2.15 pylint~=2.11 +pytest~=6.2 +pytest-cov~=2.12 diff --git a/tests/dic2owl/conftest.py b/tests/dic2owl/conftest.py new file mode 100644 index 0000000..e924bcc --- /dev/null +++ b/tests/dic2owl/conftest.py @@ -0,0 +1,149 @@ +"""PyTest fixtures for `dic2owl`.""" +# pylint: disable=import-outside-toplevel,consider-using-with,too-many-branches +# pylint: disable=redefined-outer-name,inconsistent-return-statements +from enum import Enum +from pathlib import Path +from typing import List, Optional, TYPE_CHECKING, Union + +import pytest + + +class CLI(Enum): + """Enumeration of CLIs.""" + + CIF2OWL = "cif2owl" + DIC2OWL = "dic2owl" + + +if TYPE_CHECKING: + from subprocess import CompletedProcess + from typing import Callable + + CLIRunner = Callable[ + [ + Optional[List[str]], + Optional[Union[CLI, str]], + Optional[str], + Optional[Union[Path, str]], + ], + CompletedProcess, + ] + + +@pytest.fixture(scope="session") +def clirunner() -> "CLIRunner": + """Call `dic2owl` CLI""" + from subprocess import run, CalledProcessError + from tempfile import TemporaryDirectory + + def _clirunner( + options: Optional[List[str]] = None, + cli: Optional[Union[CLI, str]] = None, + expected_error: Optional[str] = None, + run_dir: Optional[Union[Path, str]] = None, + ) -> Union["CompletedProcess", CalledProcessError]: + """Call `dic2owl` CLI + + Parameters: + options: Options with which to call `cli`, e.g., `--version`. + cli: The CLI to call, defaults to `dic2owl`. + expected_error: Sub-string expected in error output, if an error is + expected. + run_dir: The directory to use as current work directory when + running the CLI. + + Returns: + The return class for a successful call to `subprocess.run()`. + + """ + options = options or [] + + if not isinstance(options, list): + try: + options = list(options) + except TypeError as exc: + raise TypeError("options must be a list of strings.") from exc + + if cli is not None: + try: + cli = CLI(cli) + except ValueError as exc: + raise ValueError( + f"{cli!r} is not a recognized CLI. Recognized CLIs: " + f"{list(CLI.__members__)}" + ) from exc + else: + cli = CLI.DIC2OWL + + if run_dir is None: + run_dir = TemporaryDirectory() + elif isinstance(run_dir, Path): + run_dir = run_dir.resolve() + else: + try: + run_dir = Path(run_dir).resolve() + except TypeError as exc: + raise TypeError(f"{run_dir} is not a valid path.") from exc + + try: + output = run( + args=[cli.value] + options, + capture_output=True, + check=True, + cwd=run_dir.name + if isinstance(run_dir, TemporaryDirectory) + else run_dir, + text=True, + ) + if expected_error: + pytest.fail( + "Expected the CLI call to fail with an error containing " + f"the sub-string: {expected_error}" + ) + except CalledProcessError as error: + if expected_error: + if ( + expected_error in error.stdout + or expected_error in error.stderr + ): + # Expected error, found expected sub-string as well. + return error + + pytest.fail( + "The CLI call failed as expected, but the expected " + "error sub-string could not be found in stdout or " + f"stderr. Sub-string: {expected_error}" + ) + else: + pytest.fail( + "The CLI call failed when it didn't expect to.\n" + f"Information: {error}" + ) + else: + return output + finally: + if isinstance(run_dir, TemporaryDirectory): + run_dir.cleanup() + + return _clirunner + + +@pytest.fixture(scope="session") +def top_dir() -> Path: + """Return repository path.""" + return Path(__file__).parent.parent.parent.resolve() + + +@pytest.fixture(scope="session") +def cif_ttl(top_dir: Path) -> str: + """Read and return CIF-Core minimized Turtle file (generated from the + accompanying dictionary). + + NOTE: The commend conerning the file location has been removed manually + from this file. + """ + with open( + top_dir / "tests/dic2owl/static/cif_core_minimized.ttl", "r" + ) as handle: + content = handle.read() + return content diff --git a/tests/dic2owl/static/cif_core_minimized.dic b/tests/dic2owl/static/cif_core_minimized.dic new file mode 100755 index 0000000..12e1d58 --- /dev/null +++ b/tests/dic2owl/static/cif_core_minimized.dic @@ -0,0 +1,185 @@ +#\#CIF_2.0 +########################################################################## +# # +# CIF CORE DICTIONARY # +# # +########################################################################## + +data_CORE_DIC + + _dictionary.title CORE_DIC + _dictionary.class Instance + _dictionary.version 3.1.0 + _dictionary.date 2021-08-18 + _dictionary.uri + https://raw.githubusercontent.com/COMCIFS/cif_core/master/cif_core.dic + _dictionary.ddl_conformance 4.0.1 + _dictionary.namespace CifCore + _description.text +; + The CIF_CORE dictionary records all the CORE data items defined + and used with in the Crystallographic Information Framework (CIF). +; + +save_CIF_CORE + + _definition.id CIF_CORE + _definition.scope Category + _definition.class Head + _definition.update 2014-06-18 + _description.text +; + The CIF_CORE group contains the definitions of data items that + are common to all domains of crystallographic studies. +; + _name.category_id CORE_DIC + _name.object_id CIF_CORE + +save_ + +save_DIFFRACTION + + _definition.id DIFFRACTION + _definition.scope Category + _definition.class Set + _definition.update 2012-11-26 + _description.text +; + The DICTIONARY group encompassing the CORE DIFFRACTION data items defined + and used with in the Crystallographic Information Framework (CIF). +; + _name.category_id CIF_CORE + _name.object_id DIFFRACTION + +save_ + +save_DIFFRN + + _definition.id DIFFRN + _definition.scope Category + _definition.class Set + _definition.update 2012-12-13 + _description.text +; + The CATEGORY of data items used to describe the diffraction experiment. +; + _name.category_id DIFFRACTION + _name.object_id DIFFRN + +save_ + +save_diffrn.ambient_environment + + _definition.id '_diffrn.ambient_environment' + _alias.definition_id '_diffrn_ambient_environment' + _definition.update 2012-11-26 + _description.text +; + The gas or liquid environment of the crystal sample, if not air. +; + _name.category_id diffrn + _name.object_id ambient_environment + _type.purpose Describe + _type.source Recorded + _type.container Single + _type.contents Text + + loop_ + _description_example.case + 'He' + 'vacuum' + 'mother liquor' + +save_ + +save_diffrn.ambient_pressure + + _definition.id '_diffrn.ambient_pressure' + _alias.definition_id '_diffrn_ambient_pressure' + _definition.update 2012-11-26 + _description.text +; + Mean hydrostatic pressure at which intensities were measured. +; + _name.category_id diffrn + _name.object_id ambient_pressure + _type.purpose Measurand + _type.source Recorded + _type.container Single + _type.contents Real + _enumeration.range 0.0: + _units.code kilopascals + +save_ + +save_diffrn.ambient_pressure_su + + _definition.id '_diffrn.ambient_pressure_su' + + loop_ + _alias.definition_id + '_diffrn_ambient_pressure_su' + '_diffrn.ambient_pressure_esd' + + _definition.update 2021-03-03 + _description.text +; + Standard uncertainty of the mean hydrostatic pressure + at which intensities were measured. +; + _name.category_id diffrn + _name.object_id ambient_pressure_su + _name.linked_item_id '_diffrn.ambient_pressure' + _type.purpose SU + _type.source Recorded + _type.container Single + _type.contents Real + _units.code kilopascals + +save_ + +save_diffrn.ambient_pressure_gt + + _definition.id '_diffrn.ambient_pressure_gt' + _alias.definition_id '_diffrn_ambient_pressure_gt' + _definition.update 2012-12-13 + _description.text +; + Mean hydrostatic pressure above which intensities were measured. + These items allow for a pressure range to be given. + _diffrn.ambient_pressure should be used in preference to this + item when possible. +; + _name.category_id diffrn + _name.object_id ambient_pressure_gt + _type.purpose Number + _type.source Recorded + _type.container Single + _type.contents Real + _enumeration.range 0.0: + _units.code kilopascals + +save_ + +save_diffrn.ambient_pressure_lt + + _definition.id '_diffrn.ambient_pressure_lt' + _alias.definition_id '_diffrn_ambient_pressure_lt' + _definition.update 2012-12-13 + _description.text +; + Mean hydrostatic pressure below which intensities were measured. + These items allow for a pressure range to be given. + _diffrn.ambient_pressure should be used in preference to this + item when possible. +; + _name.category_id diffrn + _name.object_id ambient_pressure_lt + _type.purpose Number + _type.source Recorded + _type.container Single + _type.contents Real + _enumeration.range 0.0: + _units.code kilopascals + +save_ diff --git a/tests/dic2owl/static/cif_core_minimized.ttl b/tests/dic2owl/static/cif_core_minimized.ttl new file mode 100644 index 0000000..b9ded87 --- /dev/null +++ b/tests/dic2owl/static/cif_core_minimized.ttl @@ -0,0 +1,178 @@ +@prefix : . +@prefix cif-: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xml: . +@prefix xsd: . + + a owl:Ontology ; + owl:imports . + +:_diffrn.ambient_environment a owl:Class ; + :prefLabel "_diffrn.ambient_environment"@en ; + cif-:_alias.definition_id "_diffrn_ambient_environment"@en ; + cif-:_definition.id "_diffrn.ambient_environment"@en ; + cif-:_definition.update "2012-11-26"@en ; + cif-:_description.text """ + The gas or liquid environment of the crystal sample, if not air."""@en ; + cif-:_description_example.case "['He', 'vacuum', 'mother liquor']"@en ; + cif-:_name.category_id "diffrn"@en ; + cif-:_name.object_id "ambient_environment"@en ; + cif-:_type.container "Single"@en ; + cif-:_type.contents "Text"@en ; + cif-:_type.purpose "Describe"@en ; + cif-:_type.source "Recorded"@en ; + rdfs:subClassOf :DIFFRN, + cif-:Describe, + cif-:Recorded, + cif-:Single, + cif-:Text . + +:_diffrn.ambient_pressure a owl:Class ; + :prefLabel "_diffrn.ambient_pressure"@en ; + cif-:_alias.definition_id "_diffrn_ambient_pressure"@en ; + cif-:_definition.id "_diffrn.ambient_pressure"@en ; + cif-:_definition.update "2012-11-26"@en ; + cif-:_description.text """ + Mean hydrostatic pressure at which intensities were measured."""@en ; + cif-:_enumeration.range "0.0:"@en ; + cif-:_name.category_id "diffrn"@en ; + cif-:_name.object_id "ambient_pressure"@en ; + cif-:_type.container "Single"@en ; + cif-:_type.contents "Real"@en ; + cif-:_type.purpose "Measurand"@en ; + cif-:_type.source "Recorded"@en ; + cif-:_units.code "kilopascals"@en ; + rdfs:subClassOf :DIFFRN, + cif-:Measurand, + cif-:Real, + cif-:Recorded, + cif-:Single . + +:_diffrn.ambient_pressure_gt a owl:Class ; + :prefLabel "_diffrn.ambient_pressure_gt"@en ; + cif-:_alias.definition_id "_diffrn_ambient_pressure_gt"@en ; + cif-:_definition.id "_diffrn.ambient_pressure_gt"@en ; + cif-:_definition.update "2012-12-13"@en ; + cif-:_description.text """ + Mean hydrostatic pressure above which intensities were measured. + These items allow for a pressure range to be given. + _diffrn.ambient_pressure should be used in preference to this + item when possible."""@en ; + cif-:_enumeration.range "0.0:"@en ; + cif-:_name.category_id "diffrn"@en ; + cif-:_name.object_id "ambient_pressure_gt"@en ; + cif-:_type.container "Single"@en ; + cif-:_type.contents "Real"@en ; + cif-:_type.purpose "Number"@en ; + cif-:_type.source "Recorded"@en ; + cif-:_units.code "kilopascals"@en ; + rdfs:subClassOf :DIFFRN, + cif-:Number, + cif-:Real, + cif-:Recorded, + cif-:Single . + +:_diffrn.ambient_pressure_lt a owl:Class ; + :prefLabel "_diffrn.ambient_pressure_lt"@en ; + cif-:_alias.definition_id "_diffrn_ambient_pressure_lt"@en ; + cif-:_definition.id "_diffrn.ambient_pressure_lt"@en ; + cif-:_definition.update "2012-12-13"@en ; + cif-:_description.text """ + Mean hydrostatic pressure below which intensities were measured. + These items allow for a pressure range to be given. + _diffrn.ambient_pressure should be used in preference to this + item when possible."""@en ; + cif-:_enumeration.range "0.0:"@en ; + cif-:_name.category_id "diffrn"@en ; + cif-:_name.object_id "ambient_pressure_lt"@en ; + cif-:_type.container "Single"@en ; + cif-:_type.contents "Real"@en ; + cif-:_type.purpose "Number"@en ; + cif-:_type.source "Recorded"@en ; + cif-:_units.code "kilopascals"@en ; + rdfs:subClassOf :DIFFRN, + cif-:Number, + cif-:Real, + cif-:Recorded, + cif-:Single . + +:_diffrn.ambient_pressure_su a owl:Class ; + :prefLabel "_diffrn.ambient_pressure_su"@en ; + cif-:_alias.definition_id "['_diffrn_ambient_pressure_su', '_diffrn.ambient_pressure_esd']"@en ; + cif-:_definition.id "_diffrn.ambient_pressure_su"@en ; + cif-:_definition.update "2021-03-03"@en ; + cif-:_description.text """ + Standard uncertainty of the mean hydrostatic pressure + at which intensities were measured."""@en ; + cif-:_name.category_id "diffrn"@en ; + cif-:_name.linked_item_id "_diffrn.ambient_pressure"@en ; + cif-:_name.object_id "ambient_pressure_su"@en ; + cif-:_type.container "Single"@en ; + cif-:_type.contents "Real"@en ; + cif-:_type.purpose "SU"@en ; + cif-:_type.source "Recorded"@en ; + cif-:_units.code "kilopascals"@en ; + rdfs:subClassOf :DIFFRN, + cif-:Real, + cif-:Recorded, + cif-:SU, + cif-:Single . + +:prefLabel a owl:AnnotationProperty ; + :prefLabel "prefLabel"@en ; + rdfs:subPropertyOf rdfs:label . + +:CIF_CORE a owl:Class ; + :prefLabel "CIF_CORE"@en ; + cif-:_definition.class "Head"@en ; + cif-:_definition.id "CIF_CORE"@en ; + cif-:_definition.scope "Category"@en ; + cif-:_definition.update "2014-06-18"@en ; + cif-:_description.text """ + The CIF_CORE group contains the definitions of data items that + are common to all domains of crystallographic studies."""@en ; + cif-:_name.category_id "CORE_DIC"@en ; + cif-:_name.object_id "CIF_CORE"@en ; + rdfs:subClassOf :CORE_DIC . + +:CORE_DIC a owl:Class ; + :prefLabel "CORE_DIC"@en ; + cif-:_description.text """ + The CIF_CORE dictionary records all the CORE data items defined + and used with in the Crystallographic Information Framework (CIF)."""@en ; + cif-:_dictionary.class "Instance"@en ; + cif-:_dictionary.date "2021-08-18"@en ; + cif-:_dictionary.ddl_conformance "4.0.1"@en ; + cif-:_dictionary.namespace "CifCore"@en ; + cif-:_dictionary.title "CORE_DIC"@en ; + cif-:_dictionary.uri "https://raw.githubusercontent.com/COMCIFS/cif_core/master/cif_core.dic"@en ; + cif-:_dictionary.version "3.1.0"@en ; + rdfs:subClassOf cif-:DictionaryDefinedItem . + +:DIFFRACTION a owl:Class ; + :prefLabel "DIFFRACTION"@en ; + cif-:_definition.class "Set"@en ; + cif-:_definition.id "DIFFRACTION"@en ; + cif-:_definition.scope "Category"@en ; + cif-:_definition.update "2012-11-26"@en ; + cif-:_description.text """ + The DICTIONARY group encompassing the CORE DIFFRACTION data items defined + and used with in the Crystallographic Information Framework (CIF)."""@en ; + cif-:_name.category_id "CIF_CORE"@en ; + cif-:_name.object_id "DIFFRACTION"@en ; + rdfs:subClassOf :CIF_CORE . + +:DIFFRN a owl:Class ; + :prefLabel "DIFFRN"@en ; + cif-:_definition.class "Set"@en ; + cif-:_definition.id "DIFFRN"@en ; + cif-:_definition.scope "Category"@en ; + cif-:_definition.update "2012-12-13"@en ; + cif-:_description.text """ + The CATEGORY of data items used to describe the diffraction experiment."""@en ; + cif-:_name.category_id "DIFFRACTION"@en ; + cif-:_name.object_id "DIFFRN"@en ; + rdfs:subClassOf :DIFFRACTION . + diff --git a/tests/dic2owl/test_cli.py b/tests/dic2owl/test_cli.py new file mode 100644 index 0000000..c9967b1 --- /dev/null +++ b/tests/dic2owl/test_cli.py @@ -0,0 +1,95 @@ +"""Tests for `dic2owl.cli`.""" +# pylint: disable=import-outside-toplevel +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .conftest import CLIRunner + + +def test_version(clirunner: "CLIRunner") -> None: + """Test `--version`.""" + from dic2owl import __version__ + + output = clirunner(["--version"]) + assert output.stdout == f"dic2owl version {__version__}\n" + + +def test_local_file( + clirunner: "CLIRunner", top_dir: Path, cif_ttl: str +) -> None: + """Test a normal/default run with minimum input. + + NOTE: The commend conerning the file location has been removed from the + static test Turtle file. + """ + from tempfile import TemporaryDirectory + + with TemporaryDirectory() as tmpdir: + options = [ + str(top_dir / "tests/dic2owl/static/cif_core_minimized.dic") + ] + output = clirunner(options, run_dir=tmpdir) + + assert ( + "downloading" in output.stdout + ), f"STDOUT: {output.stdout}\nSTDERR: {output.stderr}" + + generated_ttl = "" + with open(Path(tmpdir) / "cif_core_minimized.ttl", "r") as handle: + for line in handle.readlines(): + if "dic2owl" in line: + # Skip comment line concerning the file location + pass + else: + generated_ttl += line + + assert generated_ttl == cif_ttl + + content = [_ for _ in Path(tmpdir).iterdir() if _.is_file()] + assert len(content) == 4, ( + "Since `dic2owl` downloads 3 files and the TTL file is generated " + "here, the temporary folder should contain a total of 4 files, " + f"but instead it contains {len(content)} file(s): {content}" + ) + + +def test_output(clirunner: "CLIRunner", top_dir: Path, cif_ttl: str) -> None: + """Test `--output`. + + NOTE: The commend conerning the file location has been removed from the + static test Turtle file. + """ + from tempfile import TemporaryDirectory + + with TemporaryDirectory() as tmpdir: + out_ttl = f"{tmpdir}/test.ttl" + options = [ + "--output", + out_ttl, + str(top_dir / "tests/dic2owl/static/cif_core_minimized.dic"), + ] + output = clirunner(options) + + assert ( + "downloading" in output.stdout + ), f"STDOUT: {output.stdout}\nSTDERR: {output.stderr}" + + generated_ttl = "" + with open(out_ttl, "r") as handle: + for line in handle.readlines(): + if "dic2owl" in line: + # Skip comment line concerning the file location + pass + else: + generated_ttl += line + + assert generated_ttl == cif_ttl + + content = [_ for _ in Path(tmpdir).iterdir() if _.is_file()] + assert len(content) == 1, ( + "Since `dic2owl` downloads 3 files, but in another directory and " + "only the generated TTL file is in the temporary folder, there " + "should only be 1 file in the temporary folder. But instead it " + f"contains {len(content)} file(s): {content}" + ) From 2b6210fd77d671ba592c1ec5a61a04468a71a3b8 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Mon, 30 Aug 2021 18:24:51 +0200 Subject: [PATCH 3/8] Run unit tests in CI Add generated coverage report to `.gitignore` when running `pytest` with coverage locally. --- .github/workflows/dic2owl_ci.yml | 4 ++-- .gitignore | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dic2owl_ci.yml b/.github/workflows/dic2owl_ci.yml index 0e39b03..f2222c5 100644 --- a/.github/workflows/dic2owl_ci.yml +++ b/.github/workflows/dic2owl_ci.yml @@ -41,5 +41,5 @@ jobs: - name: Lint with MyPy run: mypy --ignore-missing-imports --scripts-are-modules dic2owl/dic2owl - # - name: Run unittests - # run: pytest --cov dic2owl/dic2owl tests/ + - name: Run unit tests with PyTest + run: pytest --cov dic2owl tests/ diff --git a/.gitignore b/.gitignore index 01483d3..5866066 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__ *.pyc .mypy* +.coverage *.dic *.cif From 3e09cc77f66ad8b16911637d6e049f8d7a265629 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Mon, 30 Aug 2021 18:35:23 +0200 Subject: [PATCH 4/8] Enhance failure messages This is specifically for the CLI runner pytest fixture: Explicitly print out stdout and stderr. --- tests/dic2owl/conftest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/dic2owl/conftest.py b/tests/dic2owl/conftest.py index e924bcc..f4c06d6 100644 --- a/tests/dic2owl/conftest.py +++ b/tests/dic2owl/conftest.py @@ -112,12 +112,13 @@ def _clirunner( pytest.fail( "The CLI call failed as expected, but the expected " "error sub-string could not be found in stdout or " - f"stderr. Sub-string: {expected_error}" + f"stderr. Sub-string: {expected_error}\nSTDOUT: " + f"{error.stdout}\nSTDERR: {error.stderr}" ) else: pytest.fail( "The CLI call failed when it didn't expect to.\n" - f"Information: {error}" + f"STDOUT: {error.stdout}\nSTDERR: {error.stderr}" ) else: return output From 0e5ac7d5613f6bd8c9eaf298d5e9d83e2e729e5d Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Thu, 23 Sep 2021 17:24:35 +0200 Subject: [PATCH 5/8] First tests for dic2owl.Generator class Implement better typing all-round. --- dic2owl/dic2owl/__init__.py | 3 + dic2owl/dic2owl/dic2owl.py | 42 ++++++----- tests/dic2owl/conftest.py | 66 ++++++++++++++---- tests/dic2owl/test_cli.py | 34 +++++---- tests/dic2owl/test_dic2owl_generator.py | 92 +++++++++++++++++++++++++ 5 files changed, 187 insertions(+), 50 deletions(-) create mode 100644 tests/dic2owl/test_dic2owl_generator.py diff --git a/dic2owl/dic2owl/__init__.py b/dic2owl/dic2owl/__init__.py index cbde23a..45a8fe2 100644 --- a/dic2owl/dic2owl/__init__.py +++ b/dic2owl/dic2owl/__init__.py @@ -10,7 +10,10 @@ terminal. """ # pylint: disable=line-too-long +from .dic2owl import Generator __version__ = "0.2.0" __author__ = "Jesper Friis , Casper Welzel Andersen , Francesca Lønstad Bleken " __author_email__ = "cif@emmo-repo.eu" + +__all__ = ("Generator",) diff --git a/dic2owl/dic2owl/dic2owl.py b/dic2owl/dic2owl/dic2owl.py index 759552c..d4960d0 100644 --- a/dic2owl/dic2owl/dic2owl.py +++ b/dic2owl/dic2owl/dic2owl.py @@ -9,7 +9,7 @@ # import textwrap import types -from typing import Any, Set, Union, Sequence +from typing import TYPE_CHECKING import urllib.request from CifFile import CifDic @@ -19,24 +19,29 @@ with open(DEVNULL, "w") as handle: # pylint: disable=unspecified-encoding with redirect_stderr(handle): from emmo import World - from emmo.ontology import Ontology from owlready2 import locstr +if TYPE_CHECKING: + from _typeshed import StrPath + from typing import Any, Sequence, Set, Union + + from emmo.ontology import Ontology + # Workaround for flaw in EMMO-Python # To be removed when EMMO-Python doesn't requires ontologies to import SKOS -import emmo.ontology # noqa: E402 +import emmo.ontology # pylint: disable=wrong-import-position emmo.ontology.DEFAULT_LABEL_ANNOTATIONS = [ "http://www.w3.org/2000/01/rdf-schema#label", ] -"""The absolute, normalized path to the `ontology` directory in this -repository""" ONTOLOGY_DIR = ( Path(__file__).resolve().parent.parent.parent.joinpath("ontology") ) +"""The absolute, normalized path to the `ontology` directory in this +repository""" def lang_en(string: str) -> locstr: @@ -67,18 +72,23 @@ class Generator: """ + CIF_DDL = ( + "https://raw.githubusercontent.com/emmo-repo/CIF-ontology/main/" + "ontology/cif-ddl.ttl" + ) + # TODO: # Should `comments` be replaced with a dict `annotations` for annotating # the ontology itself? If so, we should import Dublin Core. def __init__( self, - dicfile: str, + dicfile: "StrPath", base_iri: str, - comments: Sequence[str] = (), + comments: "Sequence[str]" = (), ) -> None: self.dicfile = dicfile - self.dic = CifDic(dicfile, do_dREL=False) + self.dic = CifDic(str(self.dicfile), do_dREL=False) self.comments = comments # Create new ontology @@ -86,11 +96,7 @@ def __init__( self.onto = self.world.get_ontology(base_iri) # Load cif-ddl ontology and append it to imported ontologies - cif_ddl = ( - "https://raw.githubusercontent.com/emmo-repo/CIF-ontology/main/" - "ontology/cif-ddl.ttl" - ) - self.ddl = self.world.get_ontology(str(cif_ddl)).load() + self.ddl = self.world.get_ontology(self.CIF_DDL).load() self.ddl.sync_python_names() self.onto.imported_ontologies.append(self.ddl) @@ -98,9 +104,9 @@ def __init__( # dcterms = self.world.get_ontology('http://purl.org/dc/terms/').load() # self.onto.imported_ontologies.append(dcterms) - self.items: Set[dict] = set() + self.items: "Set[dict]" = set() - def generate(self) -> Ontology: + def generate(self) -> "Ontology": """Generate ontology for the CIF dictionary. Returns: @@ -194,7 +200,7 @@ def _add_data_value(self, item: dict) -> None: self._add_annotations(cls, item) - def _add_annotations(self, cls: Any, item: dict) -> None: + def _add_annotations(self, cls: "Any", item: dict) -> None: """Add annotations for dic item `item` to generated ontology class `cls`. @@ -228,7 +234,9 @@ def _add_metadata(self) -> None: ) -def main(dicfile: Union[str, Path], ttlfile: Union[str, Path]) -> Generator: +def main( + dicfile: "Union[str, Path]", ttlfile: "Union[str, Path]" +) -> Generator: """Main function for ontology generation. Parameters: diff --git a/tests/dic2owl/conftest.py b/tests/dic2owl/conftest.py index f4c06d6..54f2b4f 100644 --- a/tests/dic2owl/conftest.py +++ b/tests/dic2owl/conftest.py @@ -3,7 +3,7 @@ # pylint: disable=redefined-outer-name,inconsistent-return-statements from enum import Enum from pathlib import Path -from typing import List, Optional, TYPE_CHECKING, Union +from typing import TYPE_CHECKING import pytest @@ -17,7 +17,7 @@ class CLI(Enum): if TYPE_CHECKING: from subprocess import CompletedProcess - from typing import Callable + from typing import Callable, List, Optional, Union CLIRunner = Callable[ [ @@ -32,17 +32,17 @@ class CLI(Enum): @pytest.fixture(scope="session") def clirunner() -> "CLIRunner": - """Call `dic2owl` CLI""" + """Call a CLI""" from subprocess import run, CalledProcessError from tempfile import TemporaryDirectory def _clirunner( - options: Optional[List[str]] = None, - cli: Optional[Union[CLI, str]] = None, - expected_error: Optional[str] = None, - run_dir: Optional[Union[Path, str]] = None, - ) -> Union["CompletedProcess", CalledProcessError]: - """Call `dic2owl` CLI + options: "Optional[List[str]]" = None, + cli: "Optional[Union[CLI, str]]" = None, + expected_error: "Optional[str]" = None, + run_dir: "Optional[Union[Path, str]]" = None, + ) -> "Union[CompletedProcess, CalledProcessError]": + """Call a CLI Parameters: options: Options with which to call `cli`, e.g., `--version`. @@ -140,11 +140,47 @@ def cif_ttl(top_dir: Path) -> str: """Read and return CIF-Core minimized Turtle file (generated from the accompanying dictionary). - NOTE: The commend conerning the file location has been removed manually + NOTE: The comment conerning the file location has been removed manually from this file. """ - with open( - top_dir / "tests/dic2owl/static/cif_core_minimized.ttl", "r" - ) as handle: - content = handle.read() - return content + return ( + top_dir / "tests/dic2owl/static/cif_core_minimized.ttl" + ).read_text() + + +@pytest.fixture(scope="session") +def base_iri() -> str: + """Return standard CIF-Core base IRI.""" + return "http://emmo.info/CIF-ontology/ontology/cif_core#" + + +@pytest.fixture(scope="session") +def cif_dic_path(top_dir: Path) -> Path: + """Return path to minimized CIF-Core dictionary.""" + return top_dir / "tests" / "dic2owl" / "static" / "cif_core_minimized.dic" + + +@pytest.fixture +def create_location_free_ttl() -> "Callable[[Path], str]": + """Create file location comment-free turtle file.""" + + def _create_location_free_ttl(ttlfile: Path) -> str: + """Create file location comment-free turtle file. + + Parameters: + ttlfile: Path to turtle file. + + Returns: + Content of turtle file without the file location line. + """ + generated_ttl = "" + with ttlfile.open() as handle: + for line in handle.readlines(): + if "dic2owl" in line: + # Skip comment line concerning the file location + pass + else: + generated_ttl += line + return generated_ttl + + return _create_location_free_ttl diff --git a/tests/dic2owl/test_cli.py b/tests/dic2owl/test_cli.py index c9967b1..44f9b82 100644 --- a/tests/dic2owl/test_cli.py +++ b/tests/dic2owl/test_cli.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from typing import Callable + from .conftest import CLIRunner @@ -16,7 +18,10 @@ def test_version(clirunner: "CLIRunner") -> None: def test_local_file( - clirunner: "CLIRunner", top_dir: Path, cif_ttl: str + clirunner: "CLIRunner", + top_dir: Path, + cif_ttl: str, + create_location_free_ttl: "Callable[[Path], str]", ) -> None: """Test a normal/default run with minimum input. @@ -35,14 +40,9 @@ def test_local_file( "downloading" in output.stdout ), f"STDOUT: {output.stdout}\nSTDERR: {output.stderr}" - generated_ttl = "" - with open(Path(tmpdir) / "cif_core_minimized.ttl", "r") as handle: - for line in handle.readlines(): - if "dic2owl" in line: - # Skip comment line concerning the file location - pass - else: - generated_ttl += line + generated_ttl = create_location_free_ttl( + Path(tmpdir) / "cif_core_minimized.ttl" + ) assert generated_ttl == cif_ttl @@ -54,7 +54,12 @@ def test_local_file( ) -def test_output(clirunner: "CLIRunner", top_dir: Path, cif_ttl: str) -> None: +def test_output( + clirunner: "CLIRunner", + top_dir: Path, + cif_ttl: str, + create_location_free_ttl: "Callable[[Path], str]", +) -> None: """Test `--output`. NOTE: The commend conerning the file location has been removed from the @@ -75,14 +80,7 @@ def test_output(clirunner: "CLIRunner", top_dir: Path, cif_ttl: str) -> None: "downloading" in output.stdout ), f"STDOUT: {output.stdout}\nSTDERR: {output.stderr}" - generated_ttl = "" - with open(out_ttl, "r") as handle: - for line in handle.readlines(): - if "dic2owl" in line: - # Skip comment line concerning the file location - pass - else: - generated_ttl += line + generated_ttl = create_location_free_ttl(Path(out_ttl)) assert generated_ttl == cif_ttl diff --git a/tests/dic2owl/test_dic2owl_generator.py b/tests/dic2owl/test_dic2owl_generator.py new file mode 100644 index 0000000..5015bde --- /dev/null +++ b/tests/dic2owl/test_dic2owl_generator.py @@ -0,0 +1,92 @@ +"""Test the `dic2owl.dic2owl.Generator` class.""" +# pylint: disable=redefined-outer-name,import-outside-toplevel +from pathlib import Path +from typing import TYPE_CHECKING + +import pytest + +if TYPE_CHECKING: + from typing import Callable, List, Optional + + from dic2owl import Generator + + +@pytest.fixture(scope="session") +def sample_generator_comments() -> "List[str]": + """The comments to be used for the `sample_generator` fixture.""" + return ["This is a test."] + + +@pytest.fixture +def sample_generator( + base_iri: str, cif_dic_path: Path, sample_generator_comments: "List[str]" +) -> "Callable[[Optional[List[str]]], Generator]": + """Create a generator similar to what is tested in + `test_initialization()`.""" + from dic2owl import Generator + + def _sample_generator(comments: "Optional[List[str]]" = None) -> Generator: + """Create and return a `Generator` with specific list of metadata + comments. By default, the fixture `sample_generator_comments` is + used.""" + return Generator( + dicfile=cif_dic_path, + base_iri=base_iri, + comments=sample_generator_comments + if comments is None + else comments, + ) + + return _sample_generator + + +def test_initialization( + base_iri: str, cif_dic_path: Path, sample_generator_comments: "List[str]" +) -> None: + """Ensure a newly initialized Generator has intended ontologies and + properties.""" + from CifFile import CifDic + from dic2owl import Generator + + cif_dictionary = CifDic(str(cif_dic_path), do_dREL=False) + + generator = Generator( + dicfile=cif_dic_path, + base_iri=base_iri, + comments=sample_generator_comments, + ) + + assert generator + assert generator.dic.WriteOut() == cif_dictionary.WriteOut() + assert generator.ddl + assert generator.ddl in generator.onto.imported_ontologies + assert generator.comments == sample_generator_comments + + +def test_generate( + cif_ttl: str, + create_location_free_ttl: "Callable[[Path], str]", + sample_generator: "Callable[[Optional[List[str]]], Generator]", + sample_generator_comments: "List[str]", +) -> None: + """Test the `generate()` method.""" + from tempfile import NamedTemporaryFile + + generator = sample_generator(None) + generated_ontology = generator.generate() + + for comment in sample_generator_comments: + assert comment in generated_ontology.metadata.comment + assert ( + f"Generated with dic2owl from {generator.dicfile}" + in generated_ontology.metadata.comment + ) + + generated_ontology = sample_generator([]).generate() + + with NamedTemporaryFile() as output_turtle: + generated_ontology.save(output_turtle.name, format="turtle") + + generated_ttl = create_location_free_ttl(Path(output_turtle.name)) + + assert generated_ttl == cif_ttl From b9b7158fe69cffe7b1625c3b55dd9151e9d7c6bf Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Wed, 1 Dec 2021 18:40:09 +0100 Subject: [PATCH 6/8] Test CLI directly through the Python API as well --- tests/dic2owl/conftest.py | 155 ++++++++++++++++++++++++++++---------- tests/dic2owl/test_cli.py | 21 ++++-- 2 files changed, 130 insertions(+), 46 deletions(-) diff --git a/tests/dic2owl/conftest.py b/tests/dic2owl/conftest.py index 54f2b4f..56e0068 100644 --- a/tests/dic2owl/conftest.py +++ b/tests/dic2owl/conftest.py @@ -1,6 +1,8 @@ """PyTest fixtures for `dic2owl`.""" # pylint: disable=import-outside-toplevel,consider-using-with,too-many-branches # pylint: disable=redefined-outer-name,inconsistent-return-statements +# pylint: disable=too-many-statements +from collections import namedtuple from enum import Enum from pathlib import Path from typing import TYPE_CHECKING @@ -17,7 +19,7 @@ class CLI(Enum): if TYPE_CHECKING: from subprocess import CompletedProcess - from typing import Callable, List, Optional, Union + from typing import Callable, Dict, List, Literal, Optional, Union CLIRunner = Callable[ [ @@ -25,6 +27,7 @@ class CLI(Enum): Optional[Union[CLI, str]], Optional[str], Optional[Union[Path, str]], + bool, ], CompletedProcess, ] @@ -33,15 +36,21 @@ class CLI(Enum): @pytest.fixture(scope="session") def clirunner() -> "CLIRunner": """Call a CLI""" + from contextlib import redirect_stderr, redirect_stdout + import importlib + import os from subprocess import run, CalledProcessError from tempfile import TemporaryDirectory + CLIOutput = namedtuple("CLIOutput", ["stdout", "stderr"]) + def _clirunner( options: "Optional[List[str]]" = None, cli: "Optional[Union[CLI, str]]" = None, expected_error: "Optional[str]" = None, run_dir: "Optional[Union[Path, str]]" = None, - ) -> "Union[CompletedProcess, CalledProcessError]": + use_subprocess: bool = True, + ) -> "Union[CompletedProcess, CalledProcessError, CLIOutput]": """Call a CLI Parameters: @@ -51,9 +60,14 @@ def _clirunner( expected. run_dir: The directory to use as current work directory when running the CLI. + use_subprocess: Whether or not to run the CLI through a + `subprocess.run()` call or instead import and call + `dic2owl.cli.main()` directly. Returns: - The return class for a successful call to `subprocess.run()`. + The return class for a successful call to `subprocess.run()` or the + captured response from importing and running the `main()` function + directly. """ options = options or [] @@ -85,46 +99,107 @@ def _clirunner( except TypeError as exc: raise TypeError(f"{run_dir} is not a valid path.") from exc - try: - output = run( - args=[cli.value] + options, - capture_output=True, - check=True, - cwd=run_dir.name - if isinstance(run_dir, TemporaryDirectory) - else run_dir, - text=True, - ) - if expected_error: - pytest.fail( - "Expected the CLI call to fail with an error containing " - f"the sub-string: {expected_error}" - ) - except CalledProcessError as error: - if expected_error: - if ( - expected_error in error.stdout - or expected_error in error.stderr - ): - # Expected error, found expected sub-string as well. - return error - - pytest.fail( - "The CLI call failed as expected, but the expected " - "error sub-string could not be found in stdout or " - f"stderr. Sub-string: {expected_error}\nSTDOUT: " - f"{error.stdout}\nSTDERR: {error.stderr}" + if use_subprocess: + try: + output = run( + args=[cli.value] + options, + capture_output=True, + check=True, + cwd=run_dir.name + if isinstance(run_dir, TemporaryDirectory) + else run_dir, + text=True, ) + if expected_error: + pytest.fail( + "Expected the CLI call to fail with an error " + f"containing the sub-string: {expected_error}" + ) + except CalledProcessError as error: + if expected_error: + if ( + expected_error in error.stdout + or expected_error in error.stderr + ): + # Expected error, found expected sub-string as well. + return error + + pytest.fail( + "The CLI call failed as expected, but the expected " + "error sub-string could not be found in stdout or " + f"stderr. Sub-string: {expected_error}\nSTDOUT: " + f"{error.stdout}\nSTDERR: {error.stderr}" + ) + else: + pytest.fail( + "The CLI call failed when it didn't expect to.\n" + f"STDOUT: {error.stdout}\nSTDERR: {error.stderr}" + ) else: - pytest.fail( - "The CLI call failed when it didn't expect to.\n" - f"STDOUT: {error.stdout}\nSTDERR: {error.stderr}" - ) + return output + finally: + if isinstance(run_dir, TemporaryDirectory): + run_dir.cleanup() else: - return output - finally: - if isinstance(run_dir, TemporaryDirectory): - run_dir.cleanup() + cli_name = importlib.import_module(f"{cli.value}.cli") + + with TemporaryDirectory() as tmpdir: + stdout_path = Path(tmpdir) / "out.txt" + stderr_path = Path(tmpdir) / "err.txt" + original_cwd = os.getcwd() + try: + os.chdir( + run_dir.name + if isinstance(run_dir, TemporaryDirectory) + else run_dir + ) + with open(stdout_path, "w") as stdout, open( + stderr_path, "w" + ) as stderr: + with redirect_stdout(stdout), redirect_stderr(stderr): + cli_name.main(options if options else None) + output = CLIOutput( + stdout_path.read_text(), stderr_path.read_text() + ) + except SystemExit as exc: + output = CLIOutput( + stdout_path.read_text(), stderr_path.read_text() + ) + if str(exc) != "0": + pytest.fail( + "The CLI call failed when it didn't expect to.\n" + f"STDOUT: {output.stdout}\nSTDERR: {output.stderr}" + ) + return output + except Exception: # pylint: disable=broad-except + output = CLIOutput( + stdout_path.read_text(), stderr_path.read_text() + ) + if expected_error: + if ( + expected_error in output.stdout + or expected_error in output.stderr + ): + # Expected error, found expected sub-string as well. + return output + + pytest.fail( + "The CLI call failed as expected, but the expected " + "error sub-string could not be found in stdout or " + f"stderr. Sub-string: {expected_error}\nSTDOUT: " + f"{output.stdout}\nSTDERR: {output.stderr}" + ) + else: + pytest.fail( + "The CLI call failed when it didn't expect to.\n" + f"STDOUT: {output.stdout}\nSTDERR: {output.stderr}" + ) + else: + return output + finally: + os.chdir(original_cwd) + if isinstance(run_dir, TemporaryDirectory): + run_dir.cleanup() return _clirunner diff --git a/tests/dic2owl/test_cli.py b/tests/dic2owl/test_cli.py index 44f9b82..bd76705 100644 --- a/tests/dic2owl/test_cli.py +++ b/tests/dic2owl/test_cli.py @@ -3,29 +3,34 @@ from pathlib import Path from typing import TYPE_CHECKING +import pytest + if TYPE_CHECKING: from typing import Callable from .conftest import CLIRunner -def test_version(clirunner: "CLIRunner") -> None: +@pytest.mark.parametrize("use_subprocess", [True, False]) +def test_version(clirunner: "CLIRunner", use_subprocess: bool) -> None: """Test `--version`.""" from dic2owl import __version__ - output = clirunner(["--version"]) + output = clirunner(["--version"], use_subprocess=use_subprocess) assert output.stdout == f"dic2owl version {__version__}\n" +@pytest.mark.parametrize("use_subprocess", [True, False]) def test_local_file( clirunner: "CLIRunner", top_dir: Path, cif_ttl: str, create_location_free_ttl: "Callable[[Path], str]", + use_subprocess: bool, ) -> None: """Test a normal/default run with minimum input. - NOTE: The commend conerning the file location has been removed from the + NOTE: The comment conerning the file location has been removed from the static test Turtle file. """ from tempfile import TemporaryDirectory @@ -34,7 +39,9 @@ def test_local_file( options = [ str(top_dir / "tests/dic2owl/static/cif_core_minimized.dic") ] - output = clirunner(options, run_dir=tmpdir) + output = clirunner( + options, run_dir=tmpdir, use_subprocess=use_subprocess + ) assert ( "downloading" in output.stdout @@ -54,15 +61,17 @@ def test_local_file( ) +@pytest.mark.parametrize("use_subprocess", [True, False]) def test_output( clirunner: "CLIRunner", top_dir: Path, cif_ttl: str, create_location_free_ttl: "Callable[[Path], str]", + use_subprocess: bool, ) -> None: """Test `--output`. - NOTE: The commend conerning the file location has been removed from the + NOTE: The comment conerning the file location has been removed from the static test Turtle file. """ from tempfile import TemporaryDirectory @@ -74,7 +83,7 @@ def test_output( out_ttl, str(top_dir / "tests/dic2owl/static/cif_core_minimized.dic"), ] - output = clirunner(options) + output = clirunner(options, use_subprocess=use_subprocess) assert ( "downloading" in output.stdout From 691790c16f28f37f8e97056edb95b7e4fe545a91 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Fri, 3 Dec 2021 08:56:18 +0100 Subject: [PATCH 7/8] Update pre-commit hooks --- .pre-commit-config.yaml | 4 ++-- tests/dic2owl/conftest.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e895e6d..ee1b286 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,14 +7,14 @@ repos: exclude: .*(\.md|\.ttl|\.cif)$ - repo: https://github.com/ambv/black - rev: 21.7b0 + rev: 21.11b1 hooks: - id: black args: - --config=dic2owl/pyproject.toml - repo: https://github.com/pycqa/pylint - rev: 'v2.10.1' + rev: 'v2.12.1' hooks: - id: pylint args: diff --git a/tests/dic2owl/conftest.py b/tests/dic2owl/conftest.py index 56e0068..df3efde 100644 --- a/tests/dic2owl/conftest.py +++ b/tests/dic2owl/conftest.py @@ -19,7 +19,7 @@ class CLI(Enum): if TYPE_CHECKING: from subprocess import CompletedProcess - from typing import Callable, Dict, List, Literal, Optional, Union + from typing import Callable, List, Optional, Union CLIRunner = Callable[ [ From 88babb9634280fddc1ba7c69d39032ceb2caca05 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Fri, 3 Dec 2021 09:00:11 +0100 Subject: [PATCH 8/8] Make sure CI is running in more cases --- .github/workflows/dic2owl_ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/dic2owl_ci.yml b/.github/workflows/dic2owl_ci.yml index f2222c5..798ca1a 100644 --- a/.github/workflows/dic2owl_ci.yml +++ b/.github/workflows/dic2owl_ci.yml @@ -4,6 +4,8 @@ on: push: paths: - 'dic2owl/**' + - 'tests/**' + - '.github/**' jobs: