Skip to content

Commit

Permalink
Add tests for executable
Browse files Browse the repository at this point in the history
  • Loading branch information
JanCBrammer committed Sep 25, 2024
1 parent 08dcd9c commit 290f547
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 5 deletions.
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ INCHI-1-TEST/data/
INCHI-1-TEST/docs/
INCHI-1-TEST/libs/
INCHI-1-TEST/exes/
INCHI-1-TEST/tests/
**/__pycache__/
13 changes: 13 additions & 0 deletions .github/actions/compile_inchi_exe/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Compile InChI executable from triggering commit

runs:
using: "composite"
steps:
- run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE" # https://github.com/actions/runner-images/issues/6775
mkdir "$GITHUB_WORKSPACE/$EXE_DIR"
./INCHI-1-TEST/compile_inchi.sh $COMMIT "$GITHUB_WORKSPACE/$EXE_DIR" exe
shell: bash
env:
COMMIT: ${{ github.sha }}
EXE_DIR: INCHI-1-TEST/exes
12 changes: 11 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install -e INCHI-1-TEST[invariance-tests]
- uses: ./.github/actions/compile_inchi_exe
- name: Run executable tests
run: pytest INCHI-1-TEST/tests/test_executable

- uses: ./.github/actions/compile_inchi_lib
- uses: ./.github/actions/regression_tests
with:
Expand All @@ -41,7 +46,7 @@ jobs:
steps:
- name: Install build and test environment
run: |
apk add bash git musl-dev gcc make python3 py-pip
apk add bash git musl-dev gcc g++ make python3 py-pip
# We need to install git before checking out the repository.
# Otherwise, the repository will be downloaded using the GitHub REST API instead of git.
- uses: actions/checkout@v4
Expand All @@ -59,6 +64,11 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install -e INCHI-1-TEST
- uses: ./.github/actions/compile_inchi_exe
- name: Run executable tests
run: pytest INCHI-1-TEST/tests/test_executable

- uses: ./.github/actions/compile_inchi_lib
- uses: ./.github/actions/regression_tests
with:
Expand Down
9 changes: 9 additions & 0 deletions INCHI-1-TEST/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,22 @@ RUN mkdir $lib_dir && for version in $inchi_versions; do \
/inchi/INCHI-1-TEST/compile_inchi.sh "$version" "$lib_dir" lib || exit 1; \
done

ENV exe_dir="/inchi/INCHI-1-TEST/exes"
RUN for version in $inchi_versions; do \
version_dir="${exe_dir}/${version}"; \
mkdir -p $version_dir; \
/inchi/INCHI-1-TEST/compile_inchi.sh "$version" "$version_dir" exe || exit 1; \
done

FROM gcc:14-bookworm AS inchi_test

WORKDIR /inchi

# Include only what's necessary for running the tests.
COPY --from=inchi_compilation /inchi/INCHI-1-TEST/src /inchi/INCHI-1-TEST/src
COPY --from=inchi_compilation /inchi/INCHI-1-TEST/libs /inchi/INCHI-1-TEST/libs
COPY --from=inchi_compilation /inchi/INCHI-1-TEST/exes /inchi/INCHI-1-TEST/exes
COPY --from=inchi_compilation /inchi/INCHI-1-TEST/tests /inchi/INCHI-1-TEST/tests
COPY --from=inchi_compilation /inchi/INCHI-1-TEST/pyproject.toml /inchi/INCHI-1-TEST/pyproject.toml
COPY --from=inchi_compilation /inchi/INCHI-1-TEST/install.sh /inchi/INCHI-1-TEST/install.sh

Expand Down
2 changes: 1 addition & 1 deletion INCHI-1-TEST/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

apt update && apt install -y python3-pip cmake
python3 -m pip install --upgrade --break-system-packages pip
python3 -m pip install --break-system-packages -e .[invariance-tests,development]
python3 -m pip install --break-system-packages -e .[invariance-tests]
# Make `python3` available as `python`.
update-alternatives --install /usr/bin/python python /usr/bin/python3 10
3 changes: 1 addition & 2 deletions INCHI-1-TEST/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
name = "inchi_tests"
version = "1.0.0"
requires-python = ">=3.11"
dependencies = ["pydantic == 2.7.1"]
dependencies = ["pydantic == 2.7.1", "pytest == 8.3.3"]

[project.optional-dependencies]
# FIXME: We're forcing numpy major version for now until https://github.com/kuelumbus/rdkit-pypi/issues/102 is resolved.
invariance-tests = ["rdkit == 2023.9.6", "numpy < 2.0.0"]
development = ["pytest"]

[project.scripts]
run-tests = "inchi_tests.run_tests:main"
Expand Down
19 changes: 19 additions & 0 deletions INCHI-1-TEST/tests/test_executable/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Executable tests

Test specific behaviors of the executable.
Ensure that specific input (molfile and arguments) elicits specific output (e.g., error).

Where possible, stick to the following principles:
- Define input inline rather than using external files (having entire test scenario on screen is nice).
- Keep number of tests per file to a minimum.
- When replicating a bug, reference the (GitHub) issue number in the file name (e.g., `test_github_42.py`).

Have a look at the existing tests for examples on how to write a test.

Run with `pytest INCHI-1-TEST/tests/test_executable`.
Note that by default, the tests expect the InChI executable to live at `INCHI-1-TEST/exes/inchi-1`.
You can specify another InChI executable like so:

```shell
pytest INCHI-1-TEST/tests/test_executable --exe-path=path/to/executable
```
31 changes: 31 additions & 0 deletions INCHI-1-TEST/tests/test_executable/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import pytest
import subprocess
from typing import Callable
from pathlib import Path


def pytest_addoption(parser):
parser.addoption(
"--exe-path",
action="store",
default="INCHI-1-TEST/exes/inchi-1",
help="Absolute path to the InChI executable.",
)


@pytest.fixture
def run_inchi_exe(request) -> Callable:
def _run_inchi_exe(
molfile_path: str, args: str = ""
) -> subprocess.CompletedProcess:

exe_path: str = request.config.getoption("--exe-path")
if not Path(exe_path).exists():
raise FileNotFoundError(f"InChI executable not found at {exe_path}.")

return subprocess.run(
[exe_path, molfile_path, args],
capture_output=True,
)

return _run_inchi_exe
18 changes: 18 additions & 0 deletions INCHI-1-TEST/tests/test_executable/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import Callable
from pathlib import Path


def tmp_molfile(molfile: Callable) -> Callable:
"""
Creates a temporary molfile and returns its path.
`molfile` must return a string.
"""

def get_molfile_path(tmp_path: Path) -> str:
molfile_path = tmp_path.joinpath("tmp.mol")
molfile_path.write_text(molfile())

return str(molfile_path)

return get_molfile_path
113 changes: 113 additions & 0 deletions INCHI-1-TEST/tests/test_executable/test_github_40.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import pytest
from helpers import tmp_molfile


@pytest.fixture
@tmp_molfile
def molfile():
return """(R)-SDP
ChemDraw08122419562D
43 50 0 0 1 0 0 0 0 0999 V2000
-0.7846 -1.0799 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.4991 -1.4924 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.2136 -1.0799 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.2136 -0.2549 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.4991 0.1576 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.7813 0.9328 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
-1.2510 1.5648 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.5331 2.3400 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-1.0028 2.9720 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.1904 2.8288 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.0918 2.0535 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.4385 1.4215 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.5937 1.0761 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.1240 0.4441 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.9365 0.5873 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-4.2187 1.3626 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.6884 1.9946 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.8759 1.8513 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.7846 -0.2549 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.4849 0.6674 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 1.3349 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.7846 1.0799 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.7846 0.2549 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1.4991 -0.1576 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1.7813 -0.9328 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
2.5937 -1.0761 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
2.8759 -1.8513 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
3.6884 -1.9946 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
4.2187 -1.3626 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
3.9365 -0.5873 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
3.1240 -0.4441 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1.2510 -1.5648 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.4385 -1.4215 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.0918 -2.0535 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.1904 -2.8288 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1.0028 -2.9720 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1.5331 -2.3400 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
2.2136 0.2549 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
2.2136 1.0799 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1.4991 1.4924 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.4849 -0.6674 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 -1.3349 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1 2 2 0 0
2 3 1 0 0
3 4 2 0 0
4 5 1 0 0
5 6 1 0 0
6 7 1 0 0
7 8 2 0 0
8 9 1 0 0
9 10 2 0 0
10 11 1 0 0
11 12 2 0 0
7 12 1 0 0
6 13 1 0 0
13 14 2 0 0
14 15 1 0 0
15 16 2 0 0
16 17 1 0 0
17 18 2 0 0
13 18 1 0 0
5 19 2 0 0
1 19 1 0 0
20 19 1 1 0
20 21 1 0 0
21 22 1 0 0
22 23 1 0 0
23 24 2 0 0
20 24 1 0 0
24 25 1 0 0
25 26 1 0 0
26 27 1 0 0
27 28 2 0 0
28 29 1 0 0
29 30 2 0 0
30 31 1 0 0
31 32 2 0 0
27 32 1 0 0
26 33 1 0 0
33 34 2 0 0
34 35 1 0 0
35 36 2 0 0
36 37 1 0 0
37 38 2 0 0
33 38 1 0 0
25 39 2 0 0
39 40 1 0 0
40 41 2 0 0
23 41 1 0 0
20 42 1 6 0
42 43 1 0 0
1 43 1 0 0
M END
"""


@pytest.mark.xfail(strict=True, raises=AssertionError)
def test_spiro_compound_chiral(molfile, run_inchi_exe):
result = run_inchi_exe(molfile)

assert "Warning (Not chiral) structure #1." not in result.stderr.decode()
57 changes: 57 additions & 0 deletions INCHI-1-TEST/tests/test_executable/test_github_52.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import pytest
from helpers import tmp_molfile


@pytest.fixture
@tmp_molfile
def molfile_empty_bondblock():
return """
-INDIGO-08292417452D
0 0 0 0 0 0 0 0 0 0 0 V3000
M V30 BEGIN CTAB
M V30 COUNTS 1 0 0 0 0
M V30 BEGIN ATOM
M V30 1 C 9.35 -4.8 0.0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 END BOND
M V30 END CTAB
M END
"""


@pytest.fixture
@tmp_molfile
def molfile_no_bondblock():
return """
-INDIGO-08292417452D
0 0 0 0 0 0 0 0 0 0 0 V3000
M V30 BEGIN CTAB
M V30 COUNTS 1 0 0 0 0
M V30 BEGIN ATOM
M V30 1 C 9.35 -4.8 0.0 0
M V30 END ATOM
M V30 END CTAB
M END
"""


@pytest.mark.xfail(strict=True, raises=AssertionError)
def test_empty_bondblock(molfile_empty_bondblock, run_inchi_exe):
result = run_inchi_exe(molfile_empty_bondblock)

assert (
"Error 71 (no InChI; Error: No V3000 CTAB end marker) inp structure #1."
not in result.stderr.decode()
)


def test_no_bondblock(molfile_no_bondblock, run_inchi_exe):
result = run_inchi_exe(molfile_no_bondblock)

assert (
"Error 71 (no InChI; Error: No V3000 CTAB end marker) inp structure #1."
not in result.stderr.decode()
)

0 comments on commit 290f547

Please sign in to comment.