diff --git a/.github/workflows/test_on_push.yml b/.github/workflows/test_on_push.yml index c80e674..eb1aa67 100644 --- a/.github/workflows/test_on_push.yml +++ b/.github/workflows/test_on_push.yml @@ -1,4 +1,4 @@ -name: Test template generation +name: Test template and generated project on: workflow_dispatch: @@ -7,7 +7,6 @@ on: branches: - main - jobs: style: runs-on: ubuntu-latest @@ -32,7 +31,7 @@ jobs: os: [ubuntu-latest, macos-13, macos-14, windows-latest] python-version: ["3.9", "3.10", "3.11", "3.12"] name: - Template Tests (${{ matrix.os }} / Python ${{ matrix.python-version }}) + Template Generation/Project Tests (${{ matrix.os }} / Python ${{ matrix.python-version }}) steps: - name: Checkout pybamm-cookiecutter uses: actions/checkout@v4 @@ -51,9 +50,13 @@ jobs: - name: Install nox run: uv pip install nox[uv] - - name: Test Template Generation + - name: Test template generation + run: | + nox -s template-tests + + - name: Test project units run: | - nox -s test-generation + nox -s project-tests - name: Run coverage tests if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' @@ -87,3 +90,36 @@ jobs: - name: Check if the documentation can be built run: python -m nox -s docs + + generated_project_tests: + needs: [style, template_test] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-13, macos-14, windows-latest] + python-version: ["3.9", "3.10", "3.11", "3.12"] + name: + Generated Project Tests (${{ matrix.os }} / Python ${{ matrix.python-version }}) + steps: + - name: Checkout pybamm-cookiecutter + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + id: setup-python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install cookiecutter and pipx + run: python -m pip install cookiecutter pipx + + - name: Generate a project using cookiecutter + run: | + cookiecutter . --no-input + + - name: Install nox and test generated project + working-directory: ./pybamm_example_project + run: | + pipx install nox + pipx run nox -s generated-project-tests diff --git a/cookiecutter.json b/cookiecutter.json index 677bc4c..5755eb7 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -1,5 +1,5 @@ { - "project_name": "pybamm-example-project", + "project_name": "pybamm_example_project", "platform": [ "github", "gitlab" @@ -20,6 +20,7 @@ "mypy": false, "__year": "{% now 'utc', '%Y' %}", "__project_slug": "{{ cookiecutter.project_name | lower | replace('-', '_') | replace('.', '_') }}", + "_copy_without_render": ["*.yml"], "__type": "{{ 'pure' }}", "__answers": "", "__ci": "{{ cookiecutter.platform }}", @@ -31,7 +32,7 @@ "gitlab": "GitLab" }, "org": "The name of your organisation (or username, if you are not part of an organisation)", - "url": "The URL to your repository", + "url": "The URL to your repository", "full_name": "Your name", "email": "Your email", "project_short_description": "A short description of your project", diff --git a/noxfile.py b/noxfile.py index a883b4f..8abfae1 100644 --- a/noxfile.py +++ b/noxfile.py @@ -36,12 +36,19 @@ def build_docs(session: nox.Session) -> None: "build/html/", ) -@nox.session(name="test-generation") +@nox.session(name="template-tests") def run_template_generation(session): """Run the tests for testing template generation""" session.install("setuptools", silent=False) session.install("-e", ".[dev]", silent=False) - session.run("pytest", "tests") + session.run("pytest", "tests/template_tests") + +@nox.session(name="project-tests") +def run_project_tests(session): + """Run the tests for testing template generation""" + session.install("setuptools", silent=False) + session.install("-e", ".[dev]", silent=False) + session.run("pytest", "tests/project_tests") @nox.session(name="coverage") def run_coverage(session): diff --git a/tests/test_entry_points.py b/tests/project_tests/test_entry_points.py similarity index 100% rename from tests/test_entry_points.py rename to tests/project_tests/test_entry_points.py diff --git a/tests/test_project_generation.py b/tests/template_tests/test_project_generation.py similarity index 95% rename from tests/test_project_generation.py rename to tests/template_tests/test_project_generation.py index 6e959d4..811bf7f 100644 --- a/tests/test_project_generation.py +++ b/tests/template_tests/test_project_generation.py @@ -12,7 +12,7 @@ def test_bake_project(cookies: Cookies): result = cookies.bake() assert result.exit_code == 0, f"Exited with code {result.exit_code}, expected 0" assert result.exception is None, result.exception - assert result.project_path.name == "pybamm-example-project" + assert result.project_path.name == "pybamm_example_project" assert result.project_path.is_dir(), f"Project directory {result.project_path} not found" diff --git a/{{cookiecutter.project_name}}/.github/workflows/test_on_push.yml b/{{cookiecutter.project_name}}/.github/workflows/test_on_push.yml new file mode 100644 index 0000000..e07ad50 --- /dev/null +++ b/{{cookiecutter.project_name}}/.github/workflows/test_on_push.yml @@ -0,0 +1,92 @@ +name: Project Tests + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +jobs: + style: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + + - name: Check style + run: | + python -m pip install pre-commit + pre-commit run -a + + test_project: + needs: style + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-13, macos-14, windows-latest] + python-version: ["3.9", "3.10", "3.11", "3.12"] + name: + Template Generation/Project Tests (${{ matrix.os }} / Python ${{ matrix.python-version }}) + steps: + - name: Checkout pybamm-cookiecutter + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + id: setup-python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Set up uv + uses: yezz123/setup-uv@v4 + with: + uv-venv: ".venv" + + - name: Install nox + run: uv pip install nox[uv] + + - name: Test project units + run: | + nox -s generated-project-tests + + - name: Test user tests + run: | + nox -s user-tests + + - name: Run coverage tests + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' + run: python -m nox -s coverage + + - name: Upload coverage report + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' + uses: codecov/codecov-action@v4.5.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + run_doctests: + needs: style + runs-on: ubuntu-latest + name: Doctests (ubuntu-latest / Python 3.12) + + steps: + - name: Check out pybamm-cookiecutter repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + cache: 'pip' + + - name: Install nox + run: python -m pip install nox + + - name: Check if the documentation can be built + run: python -m nox -s docs diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 1a35bdd..76cb1bb 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -1,20 +1,67 @@ import nox +from pathlib import Path +import os # Options to modify nox behaviour +nox.options.default_venv_backend = "uv|virtualenv" nox.options.reuse_existing_virtualenvs = True +VENV_DIR = Path("./venv").resolve() + @nox.session(name="docs") -def build_docs(session): +def build_docs(session: nox.Session) -> None: """Build the documentation and load it in a browser tab, rebuilding on changes.""" - envbindir = session.bin session.install("-e", ".[docs]") with session.chdir("docs/"): - session.run( - "sphinx-autobuild", - "-j", - "auto", - "--open-browser", - "-qT", - ".", - f"{envbindir}/../tmp/html", - ) + # For local development + if session.interactive: + session.run( + "sphinx-autobuild", + "-j", + "auto", + "--open-browser", + "-qT", + ".", + "build/html/", + ) + # For CI testing if documentation builds + else: + session.run( + "sphinx-build", + "-b", + "html", + "-W", + "--keep-going", + ".", + "build/html/", + ) + +@nox.session(name="generated-project-tests") +def run_generated_project_tests(session): + """Run the tests for testing units inside generated project""" + session.install("setuptools", silent=False) + session.install("-e", ".[dev]", silent=False) + session.run("pytest", "tests/generated_project_tests") + +@nox.session(name="user-tests") +def run_user_tests(session): + """Run the tests for testing user written tests""" + session.install("setuptools", silent=False) + session.install("-e", ".[dev]", silent=False) + session.run("pytest", "tests/user_tests") + +@nox.session(name="coverage") +def run_coverage(session): + """Run the coverage tests and generate an XML report.""" + session.install("setuptools", silent=False) + session.install("coverage", silent=False) + session.install("-e", ".[all,dev,jax]", silent=False) + session.run("pytest", "--cov=src/pybamm_cookiecutter", "--cov-report=xml", "tests/") + +@nox.session(name="dev") +def set_dev(session): + """Install pybamm-cookiecutter in editable mode""" + session.install("virtualenv") + session.run("virtualenv", os.fsdecode(VENV_DIR), silent=True) + python = os.fsdecode(VENV_DIR.joinpath("bin/python")) + session.run(python, "-m", "pip", "install", "-e", ".[dev]") diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index cdb91d3..9d9c9bd 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -47,7 +47,7 @@ classifiers = [ {%- if cookiecutter.backend == "hatch" %} dynamic = ["version"] {%- endif %} -dependencies = ["pybamm", "cookiecutter"] +dependencies = ["pybamm",] [project.optional-dependencies] dev = [ @@ -55,7 +55,6 @@ dev = [ "pytest-cov >=3", "nox[uv]", "pre-commit", - "pytest-cookies", ] docs = [ "sphinx", diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py index 4b10162..7e3555f 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.__project_slug}}/__init__.py @@ -13,18 +13,12 @@ {# keep this line here for newline #} {%- if cookiecutter.mypy %} __all__: list[str] = [ - "__version__", - "pybamm", - "parameter_sets", - "Model", - "models", -] {%- else %} __all__ = [ +{%- endif %} "__version__", "pybamm", "parameter_sets", "Model", "models", ] -{%- endif %} diff --git a/{{cookiecutter.project_name}}/tests/generated_project_tests/test_entry_points.py b/{{cookiecutter.project_name}}/tests/generated_project_tests/test_entry_points.py new file mode 100644 index 0000000..15c9541 --- /dev/null +++ b/{{cookiecutter.project_name}}/tests/generated_project_tests/test_entry_points.py @@ -0,0 +1,43 @@ +import pytest +import {{ cookiecutter.project_name }} +import importlib.util +import sys +from pathlib import Path + +def test_parameter_sets_entry_points(): + """Test if the parameter_sets via entry points are loaded correctly.""" + + entry_points = list({{ cookiecutter.project_name }}.parameter_sets) + parameter_sets = Path("src/{{ cookiecutter.project_name }}/parameters/input/").glob("*.py") + # Making a list of parameter sets in the parameters/input directory + parameter_sets = [x.stem for x in parameter_sets] + + assert parameter_sets == entry_points, "Entry points missing either in pyproject.toml or in the input directory" + +def test_parameter_sets_entry_point_load(): + """Testing if the values get loaded via parameter entry points and are equal when loaded through entry points""" + # Loading parameter_sets through entry points + parameters = {{ cookiecutter.project_name }}.parameter_sets['Chen2020'] + # Loading parameter sets through the source file by dynamically loading Chen2020.py as a module + spec = importlib.util.spec_from_file_location("Chen2020mod", "src/{{ cookiecutter.project_name }}/parameters/input/Chen2020.py") + chen_module = importlib.util.module_from_spec(spec) + sys.modules["Chen2020mod"] = chen_module + spec.loader.exec_module(chen_module) + parameters_from_file = chen_module.get_parameter_values() + assert parameters.keys() == parameters_from_file.keys(), f"The keys in the module and local input file are not the same, expected {parameters.keys} got {parameters_from_file.keys()}" + +def test_model_entry_points(): + """Test if the models via entry points are loaded correctly.""" + + entry_points = list({{ cookiecutter.project_name }}.models) + models = Path("src/{{ cookiecutter.project_name }}/models/input/").glob("*.py") + # Making a list Parameter sets in the parameters/input directory + models = [x.stem for x in models] + + assert models == entry_points, "Entry points missing either in pyproject.toml or in the input directory" + +def test_model_entry_point_load(): + """Testing if the model gets initialised and returned.""" + # Loading parameter_sets through entry points + model_instance = {{ cookiecutter.project_name }}.Model("SPM") + assert model_instance is not None diff --git a/{{cookiecutter.project_name}}/tests/test_package.py b/{{cookiecutter.project_name}}/tests/user_tests/test_package.py similarity index 100% rename from {{cookiecutter.project_name}}/tests/test_package.py rename to {{cookiecutter.project_name}}/tests/user_tests/test_package.py