Skip to content

Commit

Permalink
Add ci workflow to run unit tests on pr or merge into main branches S…
Browse files Browse the repository at this point in the history
…CMSUITE-9942 SO107
  • Loading branch information
dormrod committed Aug 15, 2024
1 parent a4d9060 commit e664fb9
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 23 deletions.
136 changes: 136 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: CI

on:
# Run on creating or updating a PR
pull_request:
types: [opened, synchronize, reopened]

# And pushing to the trunk or fix branches
push:
branches:
- trunk
- 'fix*'

jobs:

build_and_test:
# Run on ubuntu, mac and windows for python 3.8 (in AMS python stack) and 3.11
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8", "3.11"]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Move Repo To SCM Namespace and Set PYTHONPATH
if: runner.os != 'Windows'
run: |
mkdir -p scm/plams
find . -mindepth 1 -maxdepth 1 -not -name 'scm' -not -name '.*' -exec mv {} scm/plams/ \;
echo "PYTHONPATH=$(pwd):$PYTHONPATH" >> $GITHUB_ENV
- name: Move Repo To SCM Namespace and Set PYTHONPATH (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -Path scm\plams -ItemType Directory -Force
Get-ChildItem -Directory | Where-Object { $_.Name -ne 'scm' } | ForEach-Object {
Move-Item -Path $_.FullName -Destination scm\plams
}
Get-ChildItem -File | ForEach-Object {
Move-Item -Path $_.FullName -Destination scm\plams
}
echo "PYTHONPATH=$(pwd);$env:PYTHONPATH" >> $env:GITHUB_ENV
- name: Set Up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
working-directory: scm/plams
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest
pip install coverage
pip install black
- name: Run Unit Tests
working-directory: scm/plams
run: |
pwd
coverage run -m pytest unit_tests
# ToDo: Bump the fail-under threshold over time until acceptable level is reached
- name: Evaluate Coverage
working-directory: scm/plams
run: coverage report --omit="unit_tests/*" -i --fail-under=30


lint:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Set Up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"

- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install black
pip install "black[jupyter]"
pip install flake8
- name: Run Black Formatting Check
run: |
# black --check -t py38 -l 120 .
echo "skip for now"

- name: Run Flake8 Check
run: |
flake8 --color never --count --config .flake8 .
build-docs:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Move Repo To SCM Namespace and Set PYTHONPATH
if: runner.os != 'Windows'
run: |
mkdir -p scm/plams
find . -mindepth 1 -maxdepth 1 -not -name 'scm' -not -name '.*' -exec mv {} scm/plams/ \;
echo "PYTHONPATH=$(pwd):$PYTHONPATH" >> $GITHUB_ENV
- name: Set Up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"

- name: Install Dependencies
working-directory: scm/plams
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
cd doc
pip install -r requirements.txt
# Turn on the -W flag when building once warnings to external links have been resolved
# ToDo: add a warning allowlist to check the warnings logged via -w
- name: Build Sphinx Docs
working-directory: scm/plams/doc
run: |
python build_plams_doc
48 changes: 30 additions & 18 deletions doc/build_plams_doc
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,57 @@ import sys
from os.path import join as opj

command = ""
#Try to locate sphinx-build executable
if 'AMSBIN' in os.environ:
command = opj(os.path.expandvars('$AMSBIN'), 'python3.5', 'bin', 'sphinx-build')
# Try to locate sphinx-build executable
if "AMSBIN" in os.environ:
command = opj(os.path.expandvars("$AMSBIN"), "python3.5", "bin", "sphinx-build")
if not os.path.exists(command):
# This might be a windows machine
command = opj(os.path.expandvars('$AMSBIN'), 'python3.5', 'Scripts', 'sphinx-build.exe')
command = opj(
os.path.expandvars("$AMSBIN"), "python3.5", "Scripts", "sphinx-build.exe"
)
if os.path.exists(command):
# starting sphinx-build on windows is tricky...
command = ['sh', opj(os.path.expandvars('$AMSBIN'), 'amspython'), '-m', 'sphinx']
command = [
"sh",
opj(os.path.expandvars("$AMSBIN"), "amspython"),
"-m",
"sphinx",
]
else:
print('Warning: AMSBIN found in environment, but failed to locate sphinx-build')
print(
"Warning: AMSBIN found in environment, but failed to locate sphinx-build"
)
command = ""
if command == "":
null = open(os.devnull, 'wb')
null = open(os.devnull, "wb")
try:
subprocess.call(['sphinx-build','--version'], stdout=null, stderr=null)
command = 'sphinx-build'
subprocess.call(["sphinx-build", "--version"], stdout=null, stderr=null)
command = "sphinx-build"
except OSError:
try:
subprocess.call(['sphinx-build2','--version'], stdout=null, stderr=null)
command = 'sphinx-build2'
subprocess.call(["sphinx-build2", "--version"], stdout=null, stderr=null)
command = "sphinx-build2"
except OSError:
print('Error: Sphinx executable not found!')
print("Error: Sphinx executable not found!")
null.close()
sys.exit(0)
null.close()

location = os.path.dirname(os.path.realpath(__file__))
#Source of documentation should be located in "source" subfolder next to this script
source = opj(location, 'source')
# Source of documentation should be located in "source" subfolder next to this script
source = opj(location, "source")

#Target can be given as command line argument, if not the "build" subfolder is used
# Target can be given as command line argument, if not the "build" subfolder is used
if len(sys.argv) > 1:
target = sys.argv[1]
else:
target = opj(location, 'build')
target = opj(location, "build")

warning_file = "build_plams_doc_warn_errors.txt"
if isinstance(command, list):
# on windows command is a list of multiple items
subprocess.call(command + [source, target])
return_code = subprocess.call(command + [source, target, "-w", warning_file])
else:
subprocess.call([command, source, target])
return_code = subprocess.call([command, source, target, "-w", warning_file])

sys.exit(return_code)
4 changes: 4 additions & 0 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
sphinx
sphinx_copybutton
sphinx_tabs
ipython
10 changes: 7 additions & 3 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def modify_signature(app, what, name, obj, options, signature, return_annotation

def setup(app):
if not tags.has("scm_theme"):
app.add_stylesheet("boxes.css")
app.add_css_file("boxes.css")
app.add_directive("warning", Danger)
app.add_directive("technical", Important)
app.connect("autodoc-process-signature", modify_signature)
Expand All @@ -41,7 +41,7 @@ def setup(app):

else:

extensions = []
extensions = ["sphinx_tabs.tabs"]

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
Expand Down Expand Up @@ -200,7 +200,11 @@ def setup(app):
# configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"python3": ("http://docs.python.org/3.8", None)}

autodoc_default_options = {"members": True, "private-members": True, "special-members": True}
autodoc_default_options = {
"members": True,
"private-members": True,
"special-members": True,
}
autodoc_member_order = "bysource"
autodoc_typehints = "none"

Expand Down
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dill==0.3.6
numpy==1.23.4
scipy==1.9.3
natsort==8.1.0
ase==3.22.1
rdkit==2024.03.1
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
],
keywords=["molecular modeling", "computational chemistry", "workflow", "python interface"],
python_requires=">=3.6",
install_requires=["dill>=0.2.4", "numpy", "scipy", "natsort"],
install_requires=["dill>=0.2.4", "numpy<2", "scipy", "natsort"],
extras_require={"chem_tools": ["ase", "rdkit"]},
packages=packages,
package_dir={"scm.plams": "."},
package_data={
Expand Down
12 changes: 12 additions & 0 deletions unit_tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import builtins
import pytest
import os

from scm.plams.core.settings import (
SafeRunSettings,
LogSettings,
Expand Down Expand Up @@ -62,3 +65,12 @@ def assert_config_as_expected(
assert isinstance(config.job.runscript, RunScriptSettings)
assert isinstance(config.jobmanager, JobManagerSettings)
assert isinstance(config, ConfigSettings)


def skip_if_no_ams_installation():
"""
Check whether the AMSBIN environment variable is set, and therefore if there is an AMS installation present.
If there is no installation, skip the test with a warning.
"""
if os.getenv("AMSBIN") is None:
pytest.skip("Skipping test as cannot find AMS installation. '$AMSBIN' environment variable is not set.")
8 changes: 7 additions & 1 deletion unit_tests/test_inputparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from importlib import reload

from scm.plams.core.settings import Settings
from scm.plams.unit_tests.test_helpers import get_mock_import_function
from scm.plams.unit_tests.test_helpers import get_mock_import_function, skip_if_no_ams_installation


@pytest.fixture
Expand Down Expand Up @@ -96,6 +96,9 @@ def test_to_dict_with_scmlibbase_succeeds(system_text_inputs):


def get_monkeypatched_input_parser(monkeypatch):
# If there is no AMS installation the input parser will not run so skip test with a warning
skip_if_no_ams_installation()

# Mock scm.libbase import failing (even when present in the env)
mock_import_function = get_mock_import_function("scm.libbase")
monkeypatch.setattr(builtins, "__import__", mock_import_function)
Expand All @@ -114,6 +117,9 @@ def get_monkeypatched_input_parser(monkeypatch):


def get_input_parser_or_skip():
# If there is no AMS installation the input parser will not run so skip test with a warning
skip_if_no_ams_installation()

from scm.plams.interfaces.adfsuite.inputparser import InputParserFacade, InputParser

# Get an instance of the input parser facade using the scm.libbase parser
Expand Down
3 changes: 3 additions & 0 deletions unit_tests/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
pass

from scm.plams import AMSJob, Settings
from scm.plams.unit_tests.test_helpers import skip_if_no_ams_installation


def test_hybrid_engine_input():
Expand Down Expand Up @@ -53,6 +54,7 @@ def test_hybrid_engine_input():
EndEngine
"""
skip_if_no_ams_installation()
job = AMSJob.from_input(AMSinput)
assert job.get_input() == AMSinput

Expand Down Expand Up @@ -105,6 +107,7 @@ def test_list_block_input():
EndEngine
"""
skip_if_no_ams_installation()
job = AMSJob.from_input(AMSinput)
assert job.get_input() == AMSinput

Expand Down

0 comments on commit e664fb9

Please sign in to comment.