From 1e2632feba490d5cea561f0b33e1381672d7c44b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 8 Sep 2025 21:48:51 -0400 Subject: [PATCH 01/19] fix minor bugs so examples run cleanly --- .../examples/ch03NiModelling/solutions/diffpy-cmi/fitBulkNi.py | 2 +- docs/examples/ch11ClusterXYZ/solutions/diffpy-cmi/fitCdSeNP.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/examples/ch03NiModelling/solutions/diffpy-cmi/fitBulkNi.py b/docs/examples/ch03NiModelling/solutions/diffpy-cmi/fitBulkNi.py index 5ff8f65..9351535 100644 --- a/docs/examples/ch03NiModelling/solutions/diffpy-cmi/fitBulkNi.py +++ b/docs/examples/ch03NiModelling/solutions/diffpy-cmi/fitBulkNi.py @@ -70,7 +70,7 @@ # If we want to run using multiprocessors, we can switch this to 'True'. # This requires that the 'psutil' python package installed. -RUN_PARALLEL = True +RUN_PARALLEL = False # Functions that will carry out the refinement ################## diff --git a/docs/examples/ch11ClusterXYZ/solutions/diffpy-cmi/fitCdSeNP.py b/docs/examples/ch11ClusterXYZ/solutions/diffpy-cmi/fitCdSeNP.py index 316948b..98fdae0 100644 --- a/docs/examples/ch11ClusterXYZ/solutions/diffpy-cmi/fitCdSeNP.py +++ b/docs/examples/ch11ClusterXYZ/solutions/diffpy-cmi/fitCdSeNP.py @@ -199,9 +199,6 @@ def plot_results(recipe, figname): diff = g - gcalc + diffzero mpl.rcParams.update(mpl.rcParamsDefault) - plt.style.use( - str(PWD.parent.parent.parent / "utils" / "billinge.mplstyle") - ) fig, ax1 = plt.subplots(1, 1) From 856596eaa3f1416d80652f158a97bbd4a3e897bb Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 8 Sep 2025 21:49:23 -0400 Subject: [PATCH 02/19] add example-generated files to gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 099e294..6fb753f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,11 @@ __pycache__/ # C extensions *.so +# ignore any data saved by the diffpy.cmi examples +docs/examples/ch*/solutions/diffpy-cmi/fig/* +docs/examples/ch*/solutions/diffpy-cmi/fit/* +docs/examples/ch*/solutions/diffpy-cmi/res/* + # Distribution / packaging .Python env/ From c379a115ac820a9c88efea08d3f603cae2691372 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 8 Sep 2025 21:58:17 -0400 Subject: [PATCH 03/19] CI: add ci for testing the pdf pack --- .github/workflows/test-pdf-pack.yml | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/test-pdf-pack.yml diff --git a/.github/workflows/test-pdf-pack.yml b/.github/workflows/test-pdf-pack.yml new file mode 100644 index 0000000..c21eac4 --- /dev/null +++ b/.github/workflows/test-pdf-pack.yml @@ -0,0 +1,61 @@ +name: Test PDF Pack Scripts + +on: + push: + branches: main + pull_request: + workflow_dispatch: + +jobs: + test-conda: + name: conda-${{ matrix.python-version }}-${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.11, 3.12, 3.13] # requires manual update + steps: + - uses: actions/checkout@v4 + - name: Set up conda + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: ${{ matrix.python-version }} + channels: conda-forge + - name: Install diffpy.cmi + run: | + conda install -y diffpy.cmi + - name: Run diffpy.cmi scripts from docs/examples + shell: bash + run: | + set -e + export MPLBACKEND=Agg + for script in docs/examples/ch*/solutions/diffpy-cmi/*.py; do + python "$script" + done + + test-pip: + name: pip-${{ matrix.python-version }}-${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.11, 3.12, 3.13] # requires manual update + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install diffpy.cmi (pip) + run: | + python -m pip install --upgrade pip + pip install diffpy.cmi + - name: Run diffpy.cmi scripts from docs/examples + shell: bash + run: | + set -e + export MPLBACKEND=Agg + for script in docs/examples/ch*/solutions/diffpy-cmi/*.py; do + python "$script" + done From 522c561d35ba3a9f540c8e3e2c2fa18f8b9123eb Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 8 Sep 2025 21:58:54 -0400 Subject: [PATCH 04/19] news --- news/test-tutorial-CI.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/test-tutorial-CI.rst diff --git a/news/test-tutorial-CI.rst b/news/test-tutorial-CI.rst new file mode 100644 index 0000000..9dfda0f --- /dev/null +++ b/news/test-tutorial-CI.rst @@ -0,0 +1,23 @@ +**Added:** + +* Add CI for testing examples of the PDF pack. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From ac326344c1e4dc8f34ae5dfd3bb544f77f509fd3 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 8 Sep 2025 22:14:47 -0400 Subject: [PATCH 05/19] add requirements to CI --- .github/workflows/test-pdf-pack.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-pdf-pack.yml b/.github/workflows/test-pdf-pack.yml index c21eac4..f43faf4 100644 --- a/.github/workflows/test-pdf-pack.yml +++ b/.github/workflows/test-pdf-pack.yml @@ -22,9 +22,12 @@ jobs: with: python-version: ${{ matrix.python-version }} channels: conda-forge - - name: Install diffpy.cmi + - name: Install diffpy.cmi and dependencies run: | conda install -y diffpy.cmi + for file in requirements/packs/*.txt; do + xargs -a "$file" conda install -y -c conda-forge + done - name: Run diffpy.cmi scripts from docs/examples shell: bash run: | @@ -51,6 +54,14 @@ jobs: run: | python -m pip install --upgrade pip pip install diffpy.cmi + + - name: Install diffpy.cmi and dependencies (pip) + run: | + python -m pip install --upgrade pip + pip install diffpy.cmi + for file in requirements/packs/*.txt; do + pip install -r "$file" + done - name: Run diffpy.cmi scripts from docs/examples shell: bash run: | From 3840f07fa043c56ce7ea073e97f05a22e9b1a3cf Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 8 Sep 2025 22:19:48 -0400 Subject: [PATCH 06/19] fix CI bug --- .github/workflows/test-pdf-pack.yml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test-pdf-pack.yml b/.github/workflows/test-pdf-pack.yml index f43faf4..ad09939 100644 --- a/.github/workflows/test-pdf-pack.yml +++ b/.github/workflows/test-pdf-pack.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.11, 3.12, 3.13] # requires manual update + python-version: [3.13] # requires manual update steps: - uses: actions/checkout@v4 - name: Set up conda @@ -25,9 +25,11 @@ jobs: - name: Install diffpy.cmi and dependencies run: | conda install -y diffpy.cmi - for file in requirements/packs/*.txt; do - xargs -a "$file" conda install -y -c conda-forge - done + conda install --file requirements/packs/core.txt -y + conda install --file requirements/packs/docs.txt -y + conda install --file requirements/packs/pdf.txt -y + conda install --file requirements/packs/plotting.txt -y + conda install --file requirements/packs/tests.txt -y - name: Run diffpy.cmi scripts from docs/examples shell: bash run: | @@ -44,24 +46,21 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.11, 3.12, 3.13] # requires manual update + python-version: [3.13] # requires manual update steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Install diffpy.cmi (pip) - run: | - python -m pip install --upgrade pip - pip install diffpy.cmi - - - name: Install diffpy.cmi and dependencies (pip) + - name: Upgrade pip and Install diffpy.cmi and other dependencies run: | python -m pip install --upgrade pip pip install diffpy.cmi - for file in requirements/packs/*.txt; do - pip install -r "$file" - done + pip install -r requirements/packs/core.txt + pip install -r requirements/packs/docs.txt + pip install -r requirements/packs/pdf.txt + pip install -r requirements/packs/plotting.txt + pip install -r requirements/packs/tests.txt - name: Run diffpy.cmi scripts from docs/examples shell: bash run: | From f02c948e96b56a20a9d1936dd172702a7f7cd3a9 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 9 Sep 2025 13:37:31 -0400 Subject: [PATCH 07/19] link error --- docs/source/tutorials/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tutorials/index.rst b/docs/source/tutorials/index.rst index 487326c..28444ee 100644 --- a/docs/source/tutorials/index.rst +++ b/docs/source/tutorials/index.rst @@ -6,7 +6,7 @@ Despite its utility, PDF fitting and analysis can be challenging. The process of understand PDFs requires time, care, and the development of intuition to connect structural models to experimental data. Hopefully by the end of this tutorial series, PDF fitting will go from being a mysterious black box to a powerful tool in your structural analysis. -Example usage of ``diffpy.cmi`` can be found at `this GitHub repo `_. +Example usage of ``diffpy.cmi`` can be found at `this GitHub repo `_. .. _structural-parameters: From bce5b861ef25b7191d5ed4d92a216240830245c0 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 11 Sep 2025 22:32:25 -0400 Subject: [PATCH 08/19] remove separate workflow file --- .github/workflows/test-pdf-pack.yml | 71 ----------------------------- 1 file changed, 71 deletions(-) delete mode 100644 .github/workflows/test-pdf-pack.yml diff --git a/.github/workflows/test-pdf-pack.yml b/.github/workflows/test-pdf-pack.yml deleted file mode 100644 index ad09939..0000000 --- a/.github/workflows/test-pdf-pack.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Test PDF Pack Scripts - -on: - push: - branches: main - pull_request: - workflow_dispatch: - -jobs: - test-conda: - name: conda-${{ matrix.python-version }}-${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.13] # requires manual update - steps: - - uses: actions/checkout@v4 - - name: Set up conda - uses: conda-incubator/setup-miniconda@v3 - with: - python-version: ${{ matrix.python-version }} - channels: conda-forge - - name: Install diffpy.cmi and dependencies - run: | - conda install -y diffpy.cmi - conda install --file requirements/packs/core.txt -y - conda install --file requirements/packs/docs.txt -y - conda install --file requirements/packs/pdf.txt -y - conda install --file requirements/packs/plotting.txt -y - conda install --file requirements/packs/tests.txt -y - - name: Run diffpy.cmi scripts from docs/examples - shell: bash - run: | - set -e - export MPLBACKEND=Agg - for script in docs/examples/ch*/solutions/diffpy-cmi/*.py; do - python "$script" - done - - test-pip: - name: pip-${{ matrix.python-version }}-${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.13] # requires manual update - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Upgrade pip and Install diffpy.cmi and other dependencies - run: | - python -m pip install --upgrade pip - pip install diffpy.cmi - pip install -r requirements/packs/core.txt - pip install -r requirements/packs/docs.txt - pip install -r requirements/packs/pdf.txt - pip install -r requirements/packs/plotting.txt - pip install -r requirements/packs/tests.txt - - name: Run diffpy.cmi scripts from docs/examples - shell: bash - run: | - set -e - export MPLBACKEND=Agg - for script in docs/examples/ch*/solutions/diffpy-cmi/*.py; do - python "$script" - done From 6d2764ef2aab012bd7fdce54ae7cb9d974b91b2b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 11 Sep 2025 22:35:51 -0400 Subject: [PATCH 09/19] test: add new test that runs tutorial scripts --- tests/test_pdf_scripts.py | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/test_pdf_scripts.py diff --git a/tests/test_pdf_scripts.py b/tests/test_pdf_scripts.py new file mode 100644 index 0000000..6726608 --- /dev/null +++ b/tests/test_pdf_scripts.py @@ -0,0 +1,52 @@ +import glob +import subprocess +from pathlib import Path + +import matplotlib.pyplot as plt +import pytest + + +def no_op(*args, **kwargs): + """A no-operation function to replace plt.show during tests.""" + pass + + +example_scripts = glob.glob("docs/examples/ch*/solutions/diffpy-cmi/*.py") + + +@pytest.mark.parametrize("script_path", example_scripts) +def test_script_execution(monkeypatch, script_path): + """Test execution of each example script while suppressing plot + display.""" + monkeypatch.setattr(plt, "show", no_op) + # Special handling for fitNPPt.py which depends on fitBulkNi.py + if script_path.endswith("fitNPPt.py"): + ni_script = script_path.replace("fitNPPt.py", "fitBulkNi.py") + ni_script_path = Path(ni_script) + if not ni_script_path.exists(): + pytest.fail( + f"Required script {ni_script} not found for {script_path}" + ) + # Run Ni calibration first + result_ni = subprocess.run( + ["python", str(ni_script_path)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + if result_ni.returncode != 0: + pytest.fail( + f"Calibration script {ni_script}", + " failed with error:\n{result_ni.stderr}", + ) + # Run rest of the scripts + result = subprocess.run( + ["python", script_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + + assert ( + result.returncode == 0 + ), f"Script {script_path} failed with error:\n{result.stderr}" From 0ef05dbd239d848ebf279426e467f4ba558b8905 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 15 Sep 2025 15:54:42 -0400 Subject: [PATCH 10/19] remove script that runs all examples --- tests/test_pdf_scripts.py | 52 --------------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 tests/test_pdf_scripts.py diff --git a/tests/test_pdf_scripts.py b/tests/test_pdf_scripts.py deleted file mode 100644 index 6726608..0000000 --- a/tests/test_pdf_scripts.py +++ /dev/null @@ -1,52 +0,0 @@ -import glob -import subprocess -from pathlib import Path - -import matplotlib.pyplot as plt -import pytest - - -def no_op(*args, **kwargs): - """A no-operation function to replace plt.show during tests.""" - pass - - -example_scripts = glob.glob("docs/examples/ch*/solutions/diffpy-cmi/*.py") - - -@pytest.mark.parametrize("script_path", example_scripts) -def test_script_execution(monkeypatch, script_path): - """Test execution of each example script while suppressing plot - display.""" - monkeypatch.setattr(plt, "show", no_op) - # Special handling for fitNPPt.py which depends on fitBulkNi.py - if script_path.endswith("fitNPPt.py"): - ni_script = script_path.replace("fitNPPt.py", "fitBulkNi.py") - ni_script_path = Path(ni_script) - if not ni_script_path.exists(): - pytest.fail( - f"Required script {ni_script} not found for {script_path}" - ) - # Run Ni calibration first - result_ni = subprocess.run( - ["python", str(ni_script_path)], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - if result_ni.returncode != 0: - pytest.fail( - f"Calibration script {ni_script}", - " failed with error:\n{result_ni.stderr}", - ) - # Run rest of the scripts - result = subprocess.run( - ["python", script_path], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - - assert ( - result.returncode == 0 - ), f"Script {script_path} failed with error:\n{result.stderr}" From 4d70e157c0aa96f0e0f8cd6c85ad2f01a482be0f Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 15 Sep 2025 15:56:37 -0400 Subject: [PATCH 11/19] add individual test files for each example dir --- tests/test_ch03.py | 18 ++++++++++++++++++ tests/test_ch05.py | 15 +++++++++++++++ tests/test_ch06.py | 16 ++++++++++++++++ tests/test_ch07.py | 16 ++++++++++++++++ tests/test_ch08.py | 16 ++++++++++++++++ tests/test_ch11.py | 15 +++++++++++++++ 6 files changed, 96 insertions(+) create mode 100644 tests/test_ch03.py create mode 100644 tests/test_ch05.py create mode 100644 tests/test_ch06.py create mode 100644 tests/test_ch07.py create mode 100644 tests/test_ch08.py create mode 100644 tests/test_ch11.py diff --git a/tests/test_ch03.py b/tests/test_ch03.py new file mode 100644 index 0000000..78cc397 --- /dev/null +++ b/tests/test_ch03.py @@ -0,0 +1,18 @@ +from pathlib import Path + +import pytest +from conftest import __examples_dir__, run_cmi_script + + +@pytest.mark.parametrize( + "relative_path", + [ + f"{__examples_dir__}/ch03NiModelling" + + "/solutions/diffpy-cmi/fitBulkNi.py", + f"{__examples_dir__}/ch03NiModelling" + + "/solutions/diffpy-cmi/fitNPPt.py", + ], +) +def test_ch03_examples(relative_path): + script_path = Path(__file__).parent.parent / relative_path + run_cmi_script(script_path) diff --git a/tests/test_ch05.py b/tests/test_ch05.py new file mode 100644 index 0000000..398b721 --- /dev/null +++ b/tests/test_ch05.py @@ -0,0 +1,15 @@ +from pathlib import Path + +import pytest +from conftest import __examples_dir__, run_cmi_script + + +@pytest.mark.parametrize( + "relative_path", + [ + f"{__examples_dir__}/ch05Fit2Phase/solutions/diffpy-cmi/fit2P.py", + ], +) +def test_ch05_examples(relative_path): + script_path = Path(__file__).parent.parent / relative_path + run_cmi_script(script_path) diff --git a/tests/test_ch06.py b/tests/test_ch06.py new file mode 100644 index 0000000..0526794 --- /dev/null +++ b/tests/test_ch06.py @@ -0,0 +1,16 @@ +from pathlib import Path + +import pytest +from conftest import __examples_dir__, run_cmi_script + + +@pytest.mark.parametrize( + "relative_path", + [ + f"{__examples_dir__}/ch06RefineCrystalStructureGen" + + "/solutions/diffpy-cmi/fitCrystalGen.py", + ], +) +def test_ch06_examples(relative_path): + script_path = Path(__file__).parent.parent / relative_path + run_cmi_script(script_path) diff --git a/tests/test_ch07.py b/tests/test_ch07.py new file mode 100644 index 0000000..152fc5a --- /dev/null +++ b/tests/test_ch07.py @@ -0,0 +1,16 @@ +from pathlib import Path + +import pytest +from conftest import __examples_dir__, run_cmi_script + + +@pytest.mark.parametrize( + "relative_path", + [ + f"{__examples_dir__}/ch07StructuralPhaseTransitions" + + "/solutions/diffpy-cmi/fitTSeries.py", + ], +) +def test_ch07_examples(relative_path): + script_path = Path(__file__).parent.parent / relative_path + run_cmi_script(script_path) diff --git a/tests/test_ch08.py b/tests/test_ch08.py new file mode 100644 index 0000000..b18a2ce --- /dev/null +++ b/tests/test_ch08.py @@ -0,0 +1,16 @@ +from pathlib import Path + +import pytest +from conftest import __examples_dir__, run_cmi_script + + +@pytest.mark.parametrize( + "relative_path", + [ + f"{__examples_dir__}/ch08NPRefinement" + + "/solutions/diffpy-cmi/fitCdSeNP.py", + ], +) +def test_ch08_examples(relative_path): + script_path = Path(__file__).parent.parent / relative_path + run_cmi_script(script_path) diff --git a/tests/test_ch11.py b/tests/test_ch11.py new file mode 100644 index 0000000..432c2ae --- /dev/null +++ b/tests/test_ch11.py @@ -0,0 +1,15 @@ +from pathlib import Path + +import pytest +from conftest import __examples_dir__, run_cmi_script + + +@pytest.mark.parametrize( + "relative_path", + [ + f"{__examples_dir__}/ch11ClusterXYZ/solutions/diffpy-cmi/fitCdSeNP.py", + ], +) +def test_ch11_examples(relative_path): + script_path = Path(__file__).parent.parent / relative_path + run_cmi_script(script_path) From f1c2ac2f923cf5433c3c4b17d3e42c570584ecc2 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 15 Sep 2025 15:57:50 -0400 Subject: [PATCH 12/19] add functions for running scripts to conftest --- tests/conftest.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index e3b6313..ca03678 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,12 @@ +import importlib.util import json from pathlib import Path +import matplotlib import pytest +__examples_dir__ = Path(__file__).parent.parent / "docs" / "examples" + @pytest.fixture def user_filesystem(tmp_path): @@ -17,3 +21,24 @@ def user_filesystem(tmp_path): json.dump(home_config_data, f) yield tmp_path + + +@pytest.fixture(scope="session", autouse=True) +def use_headless_matplotlib(): + """Force matplotlib to use a headless backend during tests.""" + matplotlib.use("Agg") + + +def load_module_from_path(path: Path): + """Load a module given an absolute Path.""" + spec = importlib.util.spec_from_file_location(path.stem, path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def run_cmi_script(script_path: Path): + """General runner for example scripts with a main().""" + module = load_module_from_path(script_path) + assert hasattr(module, "main"), f"{script_path} has no main() function" + module.main() From 5b27183ce01010a0207b152d7bc4c82977011334 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 17 Sep 2025 11:21:13 -0400 Subject: [PATCH 13/19] glob files in examples dir --- tests/conftest.py | 3 ++- tests/test_ch03.py | 19 ++++++++----------- tests/test_ch05.py | 16 ++++++++-------- tests/test_ch06.py | 17 ++++++++--------- tests/test_ch07.py | 17 ++++++++--------- tests/test_ch08.py | 17 ++++++++--------- tests/test_ch11.py | 16 ++++++++-------- 7 files changed, 50 insertions(+), 55 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ca03678..a4b3138 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -40,5 +40,6 @@ def load_module_from_path(path: Path): def run_cmi_script(script_path: Path): """General runner for example scripts with a main().""" module = load_module_from_path(script_path) - assert hasattr(module, "main"), f"{script_path} has no main() function" + if not hasattr(module, "main"): + pytest.skip(f"{script_path} has no main() function") module.main() diff --git a/tests/test_ch03.py b/tests/test_ch03.py index 78cc397..c315c43 100644 --- a/tests/test_ch03.py +++ b/tests/test_ch03.py @@ -3,16 +3,13 @@ import pytest from conftest import __examples_dir__, run_cmi_script +chapter = "ch03NiModelling" +chapter_dir = Path(__examples_dir__) / chapter +example_scripts = list(chapter_dir.rglob("*.py")) -@pytest.mark.parametrize( - "relative_path", - [ - f"{__examples_dir__}/ch03NiModelling" - + "/solutions/diffpy-cmi/fitBulkNi.py", - f"{__examples_dir__}/ch03NiModelling" - + "/solutions/diffpy-cmi/fitNPPt.py", - ], -) -def test_ch03_examples(relative_path): - script_path = Path(__file__).parent.parent / relative_path + +# Runs all example scripts in chapter 3, skips files in main() is not defined. +# Passes if script runs without error. +@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) +def test_ch03_examples(script_path): run_cmi_script(script_path) diff --git a/tests/test_ch05.py b/tests/test_ch05.py index 398b721..2347eed 100644 --- a/tests/test_ch05.py +++ b/tests/test_ch05.py @@ -3,13 +3,13 @@ import pytest from conftest import __examples_dir__, run_cmi_script +chapter = "ch05Fit2Phase" +chapter_dir = Path(__examples_dir__) / chapter +example_scripts = list(chapter_dir.rglob("*.py")) -@pytest.mark.parametrize( - "relative_path", - [ - f"{__examples_dir__}/ch05Fit2Phase/solutions/diffpy-cmi/fit2P.py", - ], -) -def test_ch05_examples(relative_path): - script_path = Path(__file__).parent.parent / relative_path + +# Runs all example scripts in chapter 5, skips files in main() is not defined. +# Passes if script runs without error. +@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) +def test_ch03_examples(script_path): run_cmi_script(script_path) diff --git a/tests/test_ch06.py b/tests/test_ch06.py index 0526794..3d790a5 100644 --- a/tests/test_ch06.py +++ b/tests/test_ch06.py @@ -3,14 +3,13 @@ import pytest from conftest import __examples_dir__, run_cmi_script +chapter = "ch06RefineCrystalStructureGen" +chapter_dir = Path(__examples_dir__) / chapter +example_scripts = list(chapter_dir.rglob("*.py")) -@pytest.mark.parametrize( - "relative_path", - [ - f"{__examples_dir__}/ch06RefineCrystalStructureGen" - + "/solutions/diffpy-cmi/fitCrystalGen.py", - ], -) -def test_ch06_examples(relative_path): - script_path = Path(__file__).parent.parent / relative_path + +# Runs all example scripts in chapter 6, skips files in main() is not defined. +# Passes if script runs without error. +@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) +def test_ch03_examples(script_path): run_cmi_script(script_path) diff --git a/tests/test_ch07.py b/tests/test_ch07.py index 152fc5a..5b60645 100644 --- a/tests/test_ch07.py +++ b/tests/test_ch07.py @@ -3,14 +3,13 @@ import pytest from conftest import __examples_dir__, run_cmi_script +chapter = "ch07StructuralPhaseTransitions" +chapter_dir = Path(__examples_dir__) / chapter +example_scripts = list(chapter_dir.rglob("*.py")) -@pytest.mark.parametrize( - "relative_path", - [ - f"{__examples_dir__}/ch07StructuralPhaseTransitions" - + "/solutions/diffpy-cmi/fitTSeries.py", - ], -) -def test_ch07_examples(relative_path): - script_path = Path(__file__).parent.parent / relative_path + +# Runs all example scripts in chapter 7, skips files in main() is not defined. +# Passes if script runs without error. +@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) +def test_ch03_examples(script_path): run_cmi_script(script_path) diff --git a/tests/test_ch08.py b/tests/test_ch08.py index b18a2ce..e2e217b 100644 --- a/tests/test_ch08.py +++ b/tests/test_ch08.py @@ -3,14 +3,13 @@ import pytest from conftest import __examples_dir__, run_cmi_script +chapter = "ch08NPRefinement" +chapter_dir = Path(__examples_dir__) / chapter +example_scripts = list(chapter_dir.rglob("*.py")) -@pytest.mark.parametrize( - "relative_path", - [ - f"{__examples_dir__}/ch08NPRefinement" - + "/solutions/diffpy-cmi/fitCdSeNP.py", - ], -) -def test_ch08_examples(relative_path): - script_path = Path(__file__).parent.parent / relative_path + +# Runs all example scripts in chapter 8, skips files in main() is not defined. +# Passes if script runs without error. +@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) +def test_ch03_examples(script_path): run_cmi_script(script_path) diff --git a/tests/test_ch11.py b/tests/test_ch11.py index 432c2ae..226d2ad 100644 --- a/tests/test_ch11.py +++ b/tests/test_ch11.py @@ -3,13 +3,13 @@ import pytest from conftest import __examples_dir__, run_cmi_script +chapter = "ch11ClusterXYZ" +chapter_dir = Path(__examples_dir__) / chapter +example_scripts = list(chapter_dir.rglob("*.py")) -@pytest.mark.parametrize( - "relative_path", - [ - f"{__examples_dir__}/ch11ClusterXYZ/solutions/diffpy-cmi/fitCdSeNP.py", - ], -) -def test_ch11_examples(relative_path): - script_path = Path(__file__).parent.parent / relative_path + +# Runs all example scripts in chapter 11, skips files in main() is not defined. +# Passes if script runs without error. +@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) +def test_ch03_examples(script_path): run_cmi_script(script_path) From d0e17cfdf781953cfb24f7e1ad519857c9eef08b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 17 Sep 2025 11:24:15 -0400 Subject: [PATCH 14/19] fix typo in comment --- tests/test_ch03.py | 2 +- tests/test_ch05.py | 2 +- tests/test_ch06.py | 2 +- tests/test_ch07.py | 2 +- tests/test_ch08.py | 2 +- tests/test_ch11.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_ch03.py b/tests/test_ch03.py index c315c43..7799d60 100644 --- a/tests/test_ch03.py +++ b/tests/test_ch03.py @@ -8,7 +8,7 @@ example_scripts = list(chapter_dir.rglob("*.py")) -# Runs all example scripts in chapter 3, skips files in main() is not defined. +# Runs all example scripts in chapter 3, skips files if main() is not defined. # Passes if script runs without error. @pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) def test_ch03_examples(script_path): diff --git a/tests/test_ch05.py b/tests/test_ch05.py index 2347eed..14e46c3 100644 --- a/tests/test_ch05.py +++ b/tests/test_ch05.py @@ -8,7 +8,7 @@ example_scripts = list(chapter_dir.rglob("*.py")) -# Runs all example scripts in chapter 5, skips files in main() is not defined. +# Runs all example scripts in chapter 5, skips files if main() is not defined. # Passes if script runs without error. @pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) def test_ch03_examples(script_path): diff --git a/tests/test_ch06.py b/tests/test_ch06.py index 3d790a5..58986e3 100644 --- a/tests/test_ch06.py +++ b/tests/test_ch06.py @@ -8,7 +8,7 @@ example_scripts = list(chapter_dir.rglob("*.py")) -# Runs all example scripts in chapter 6, skips files in main() is not defined. +# Runs all example scripts in chapter 6, skips files if main() is not defined. # Passes if script runs without error. @pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) def test_ch03_examples(script_path): diff --git a/tests/test_ch07.py b/tests/test_ch07.py index 5b60645..52b609f 100644 --- a/tests/test_ch07.py +++ b/tests/test_ch07.py @@ -8,7 +8,7 @@ example_scripts = list(chapter_dir.rglob("*.py")) -# Runs all example scripts in chapter 7, skips files in main() is not defined. +# Runs all example scripts in chapter 7, skips files if main() is not defined. # Passes if script runs without error. @pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) def test_ch03_examples(script_path): diff --git a/tests/test_ch08.py b/tests/test_ch08.py index e2e217b..011519e 100644 --- a/tests/test_ch08.py +++ b/tests/test_ch08.py @@ -8,7 +8,7 @@ example_scripts = list(chapter_dir.rglob("*.py")) -# Runs all example scripts in chapter 8, skips files in main() is not defined. +# Runs all example scripts in chapter 8, skips files if main() is not defined. # Passes if script runs without error. @pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) def test_ch03_examples(script_path): diff --git a/tests/test_ch11.py b/tests/test_ch11.py index 226d2ad..ccbb148 100644 --- a/tests/test_ch11.py +++ b/tests/test_ch11.py @@ -8,7 +8,7 @@ example_scripts = list(chapter_dir.rglob("*.py")) -# Runs all example scripts in chapter 11, skips files in main() is not defined. +# Runs all example scripts in chapter 11, skips files if main() is not defined. # Passes if script runs without error. @pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) def test_ch03_examples(script_path): From 89e89bb06e807dbf48e7eb375ca13fdd6bff4b7c Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Wed, 17 Sep 2025 18:26:11 -0400 Subject: [PATCH 15/19] add meta-test --- tests/test_examples_structure.py | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/test_examples_structure.py diff --git a/tests/test_examples_structure.py b/tests/test_examples_structure.py new file mode 100644 index 0000000..1cf1003 --- /dev/null +++ b/tests/test_examples_structure.py @@ -0,0 +1,55 @@ +import re +from pathlib import Path + +EXAMPLES_DIR = Path(__file__).parent.parent / "docs" / "examples" +TESTS_DIR = Path(__file__).parent + + +def get_example_chapters(): + """Return a dict {chapter_name: Path} for all dirs in examples/.""" + return {d.name: d for d in EXAMPLES_DIR.iterdir() if d.is_dir()} + + +def get_test_chapters(): + """Return a set of real chapter names that have test files.""" + chapters = set() + for test_file in TESTS_DIR.glob("test_ch*.py"): + m = re.match(r"test_(ch\d+)\.py", test_file.name) + if m: + prefix = m.group(1) + match = next( + ( + d.name + for d in EXAMPLES_DIR.iterdir() + if d.name.startswith(prefix) + ), + None, + ) + if match: + chapters.add(match) + return chapters + + +def test_chapters_have_valid_names_and_tests(): + """Check that all example chapters are named properly and have + tests.""" + example_chapters = get_example_chapters() + test_chapters = get_test_chapters() + errors = [] + # 1. Fail if any dir does not follow "ch" + number convention + bad_names = [ + name for name in example_chapters if not re.match(r"^ch\d+", name) + ] + if bad_names: + errors.append( + "Invalid example chapter names (must match '^ch[0-9]+'): " + + str(bad_names) + ) + # 2. Fail if any example chapter has no corresponding test + missing_tests = set(example_chapters) - test_chapters + if missing_tests: + errors.append( + "Missing test files for chapters or bad test file name " + "(must be named 'test_ch.py'): " + str(sorted(missing_tests)) + ) + assert not errors, "\n".join(errors) From f6ad035e628ddbd84ffa3f95e34ef847588206c0 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Thu, 18 Sep 2025 10:48:49 -0400 Subject: [PATCH 16/19] set qdamp and qbroad in example if instrument file not found --- docs/examples/ch03NiModelling/solutions/diffpy-cmi/fitNPPt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/examples/ch03NiModelling/solutions/diffpy-cmi/fitNPPt.py b/docs/examples/ch03NiModelling/solutions/diffpy-cmi/fitNPPt.py index 35f164d..7f9c6a8 100644 --- a/docs/examples/ch03NiModelling/solutions/diffpy-cmi/fitNPPt.py +++ b/docs/examples/ch03NiModelling/solutions/diffpy-cmi/fitNPPt.py @@ -87,6 +87,9 @@ print("The Ni example refines instrument parameters\n") print("The instrument parameters are necessary to run this fit\n") print("Please run the Ni example first\n") + print("Setting Q_damp and Q_broad to refined values\n") + QDAMP_I = 0.045298 + QBROAD_I = 0.016809 # If we want to run using multiprocessors, we can switch this to 'True'. # This requires that the 'psutil' python package installed. From 94080423ff38da0c1d984b1629c2da81d2396304 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 19 Sep 2025 17:19:22 -0400 Subject: [PATCH 17/19] test scripts in a temp dir --- tests/conftest.py | 34 ++++++------ tests/examples_exist_test.py | 25 +++++++++ tests/helpers.py | 43 +++++++++++++++ tests/test_ch03.py | 15 ------ tests/test_ch03NiModelling.py | 5 ++ tests/test_ch05.py | 15 ------ tests/test_ch05Fit2Phase.py | 5 ++ tests/test_ch06.py | 15 ------ tests/test_ch06RefineCrystalStructureGen.py | 5 ++ tests/test_ch07.py | 15 ------ tests/test_ch07StructuralPhaseTransitions.py | 5 ++ tests/test_ch08.py | 15 ------ tests/test_ch08NPRefinement.py | 5 ++ tests/test_ch11.py | 15 ------ tests/test_ch11ClusterXYZ.py | 5 ++ tests/test_examples_structure.py | 55 -------------------- 16 files changed, 117 insertions(+), 160 deletions(-) create mode 100644 tests/examples_exist_test.py create mode 100644 tests/helpers.py delete mode 100644 tests/test_ch03.py create mode 100644 tests/test_ch03NiModelling.py delete mode 100644 tests/test_ch05.py create mode 100644 tests/test_ch05Fit2Phase.py delete mode 100644 tests/test_ch06.py create mode 100644 tests/test_ch06RefineCrystalStructureGen.py delete mode 100644 tests/test_ch07.py create mode 100644 tests/test_ch07StructuralPhaseTransitions.py delete mode 100644 tests/test_ch08.py create mode 100644 tests/test_ch08NPRefinement.py delete mode 100644 tests/test_ch11.py create mode 100644 tests/test_ch11ClusterXYZ.py delete mode 100644 tests/test_examples_structure.py diff --git a/tests/conftest.py b/tests/conftest.py index a4b3138..a4f19e5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,18 @@ -import importlib.util import json +import shutil +import warnings from pathlib import Path import matplotlib import pytest -__examples_dir__ = Path(__file__).parent.parent / "docs" / "examples" +# Suppress specific UserWarnings when running mpl headless +warnings.filterwarnings( + "ignore", category=UserWarning, message=".*FigureCanvasAgg.*" +) + +EXAMPLES_DIR = Path(__file__).parent.parent / "docs" / "examples" +TESTS_DIR = Path(__file__).parent @pytest.fixture @@ -29,17 +36,14 @@ def use_headless_matplotlib(): matplotlib.use("Agg") -def load_module_from_path(path: Path): - """Load a module given an absolute Path.""" - spec = importlib.util.spec_from_file_location(path.stem, path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - +@pytest.fixture(scope="session") +def examples_tmpdir(tmp_path_factory): + """Make a temp copy of all examples for safe testing. -def run_cmi_script(script_path: Path): - """General runner for example scripts with a main().""" - module = load_module_from_path(script_path) - if not hasattr(module, "main"): - pytest.skip(f"{script_path} has no main() function") - module.main() + Removes the temp copy after tests are done. + """ + temp_dir = tmp_path_factory.mktemp("examples_copy") + temp_examples = temp_dir / "examples" + shutil.copytree(EXAMPLES_DIR, temp_examples, dirs_exist_ok=True) + yield temp_examples + shutil.rmtree(temp_dir, ignore_errors=True) diff --git a/tests/examples_exist_test.py b/tests/examples_exist_test.py new file mode 100644 index 0000000..62ce979 --- /dev/null +++ b/tests/examples_exist_test.py @@ -0,0 +1,25 @@ +from conftest import TESTS_DIR + + +def test_example_dirs_have_tests(examples_tmpdir): + """Verify that each example directory has a corresponding test + file.""" + example_dirs = [d for d in examples_tmpdir.iterdir() if d.is_dir()] + missing_tests = [] + for example_dir in example_dirs: + # Test file expected in TESTS_DIR, named e.g. test_.py + test_file = TESTS_DIR / f"test_{example_dir.name}.py" + if not test_file.exists(): + missing_tests.append(example_dir.name) + assert not missing_tests, ( + f"The following example dirs have no test file: {missing_tests}.", + "Test file must be named test_.py.", + ) + + +def test_examples_tmpdir_exists(examples_tmpdir): + """Ensure that the examples temporary directory has been created.""" + # Check the directory itself exists + assert ( + examples_tmpdir.exists() and examples_tmpdir.is_dir() + ), f"Temporary examples directory does not exist: {examples_tmpdir}" diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 0000000..51dc57d --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,43 @@ +import importlib.util +import os +from pathlib import Path + + +def load_module_from_path(path: Path): + """Load a module given an absolute Path.""" + spec = importlib.util.spec_from_file_location(path.stem, path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def run_cmi_script(script_path: Path): + """Run a script with a main() function.""" + old_cwd = os.getcwd() + os.chdir(script_path.parent) + try: + module = load_module_from_path(script_path) + module.main() + finally: + os.chdir(old_cwd) + + +def run_all_scripts_for_given_example(examples_tmpdir, test_file_path): + """Run all Python scripts in the chapter corresponding to this test + file. + + Only scripts that define a main() function will be executed. + """ + chapter_dir_name = Path(test_file_path).stem.replace("test_", "") + chapter_dir = examples_tmpdir / chapter_dir_name + assert chapter_dir.exists(), f"Chapter dir does not exist: {chapter_dir}" + + # Recursively find all .py scripts + scripts = list(chapter_dir.rglob("*.py")) + for script_path in scripts: + module = load_module_from_path(script_path) + if hasattr(module, "main"): + run_cmi_script(script_path) + else: + # automatically skip helper or non-example scripts + print(f"Skipping file without main(): {script_path.name}") diff --git a/tests/test_ch03.py b/tests/test_ch03.py deleted file mode 100644 index 7799d60..0000000 --- a/tests/test_ch03.py +++ /dev/null @@ -1,15 +0,0 @@ -from pathlib import Path - -import pytest -from conftest import __examples_dir__, run_cmi_script - -chapter = "ch03NiModelling" -chapter_dir = Path(__examples_dir__) / chapter -example_scripts = list(chapter_dir.rglob("*.py")) - - -# Runs all example scripts in chapter 3, skips files if main() is not defined. -# Passes if script runs without error. -@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) -def test_ch03_examples(script_path): - run_cmi_script(script_path) diff --git a/tests/test_ch03NiModelling.py b/tests/test_ch03NiModelling.py new file mode 100644 index 0000000..a52b13f --- /dev/null +++ b/tests/test_ch03NiModelling.py @@ -0,0 +1,5 @@ +from helpers import run_all_scripts_for_given_example + + +def test_scripts(examples_tmpdir): + run_all_scripts_for_given_example(examples_tmpdir, __file__) diff --git a/tests/test_ch05.py b/tests/test_ch05.py deleted file mode 100644 index 14e46c3..0000000 --- a/tests/test_ch05.py +++ /dev/null @@ -1,15 +0,0 @@ -from pathlib import Path - -import pytest -from conftest import __examples_dir__, run_cmi_script - -chapter = "ch05Fit2Phase" -chapter_dir = Path(__examples_dir__) / chapter -example_scripts = list(chapter_dir.rglob("*.py")) - - -# Runs all example scripts in chapter 5, skips files if main() is not defined. -# Passes if script runs without error. -@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) -def test_ch03_examples(script_path): - run_cmi_script(script_path) diff --git a/tests/test_ch05Fit2Phase.py b/tests/test_ch05Fit2Phase.py new file mode 100644 index 0000000..a52b13f --- /dev/null +++ b/tests/test_ch05Fit2Phase.py @@ -0,0 +1,5 @@ +from helpers import run_all_scripts_for_given_example + + +def test_scripts(examples_tmpdir): + run_all_scripts_for_given_example(examples_tmpdir, __file__) diff --git a/tests/test_ch06.py b/tests/test_ch06.py deleted file mode 100644 index 58986e3..0000000 --- a/tests/test_ch06.py +++ /dev/null @@ -1,15 +0,0 @@ -from pathlib import Path - -import pytest -from conftest import __examples_dir__, run_cmi_script - -chapter = "ch06RefineCrystalStructureGen" -chapter_dir = Path(__examples_dir__) / chapter -example_scripts = list(chapter_dir.rglob("*.py")) - - -# Runs all example scripts in chapter 6, skips files if main() is not defined. -# Passes if script runs without error. -@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) -def test_ch03_examples(script_path): - run_cmi_script(script_path) diff --git a/tests/test_ch06RefineCrystalStructureGen.py b/tests/test_ch06RefineCrystalStructureGen.py new file mode 100644 index 0000000..a52b13f --- /dev/null +++ b/tests/test_ch06RefineCrystalStructureGen.py @@ -0,0 +1,5 @@ +from helpers import run_all_scripts_for_given_example + + +def test_scripts(examples_tmpdir): + run_all_scripts_for_given_example(examples_tmpdir, __file__) diff --git a/tests/test_ch07.py b/tests/test_ch07.py deleted file mode 100644 index 52b609f..0000000 --- a/tests/test_ch07.py +++ /dev/null @@ -1,15 +0,0 @@ -from pathlib import Path - -import pytest -from conftest import __examples_dir__, run_cmi_script - -chapter = "ch07StructuralPhaseTransitions" -chapter_dir = Path(__examples_dir__) / chapter -example_scripts = list(chapter_dir.rglob("*.py")) - - -# Runs all example scripts in chapter 7, skips files if main() is not defined. -# Passes if script runs without error. -@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) -def test_ch03_examples(script_path): - run_cmi_script(script_path) diff --git a/tests/test_ch07StructuralPhaseTransitions.py b/tests/test_ch07StructuralPhaseTransitions.py new file mode 100644 index 0000000..a52b13f --- /dev/null +++ b/tests/test_ch07StructuralPhaseTransitions.py @@ -0,0 +1,5 @@ +from helpers import run_all_scripts_for_given_example + + +def test_scripts(examples_tmpdir): + run_all_scripts_for_given_example(examples_tmpdir, __file__) diff --git a/tests/test_ch08.py b/tests/test_ch08.py deleted file mode 100644 index 011519e..0000000 --- a/tests/test_ch08.py +++ /dev/null @@ -1,15 +0,0 @@ -from pathlib import Path - -import pytest -from conftest import __examples_dir__, run_cmi_script - -chapter = "ch08NPRefinement" -chapter_dir = Path(__examples_dir__) / chapter -example_scripts = list(chapter_dir.rglob("*.py")) - - -# Runs all example scripts in chapter 8, skips files if main() is not defined. -# Passes if script runs without error. -@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) -def test_ch03_examples(script_path): - run_cmi_script(script_path) diff --git a/tests/test_ch08NPRefinement.py b/tests/test_ch08NPRefinement.py new file mode 100644 index 0000000..a52b13f --- /dev/null +++ b/tests/test_ch08NPRefinement.py @@ -0,0 +1,5 @@ +from helpers import run_all_scripts_for_given_example + + +def test_scripts(examples_tmpdir): + run_all_scripts_for_given_example(examples_tmpdir, __file__) diff --git a/tests/test_ch11.py b/tests/test_ch11.py deleted file mode 100644 index ccbb148..0000000 --- a/tests/test_ch11.py +++ /dev/null @@ -1,15 +0,0 @@ -from pathlib import Path - -import pytest -from conftest import __examples_dir__, run_cmi_script - -chapter = "ch11ClusterXYZ" -chapter_dir = Path(__examples_dir__) / chapter -example_scripts = list(chapter_dir.rglob("*.py")) - - -# Runs all example scripts in chapter 11, skips files if main() is not defined. -# Passes if script runs without error. -@pytest.mark.parametrize("script_path", example_scripts, ids=lambda p: p.name) -def test_ch03_examples(script_path): - run_cmi_script(script_path) diff --git a/tests/test_ch11ClusterXYZ.py b/tests/test_ch11ClusterXYZ.py new file mode 100644 index 0000000..a52b13f --- /dev/null +++ b/tests/test_ch11ClusterXYZ.py @@ -0,0 +1,5 @@ +from helpers import run_all_scripts_for_given_example + + +def test_scripts(examples_tmpdir): + run_all_scripts_for_given_example(examples_tmpdir, __file__) diff --git a/tests/test_examples_structure.py b/tests/test_examples_structure.py deleted file mode 100644 index 1cf1003..0000000 --- a/tests/test_examples_structure.py +++ /dev/null @@ -1,55 +0,0 @@ -import re -from pathlib import Path - -EXAMPLES_DIR = Path(__file__).parent.parent / "docs" / "examples" -TESTS_DIR = Path(__file__).parent - - -def get_example_chapters(): - """Return a dict {chapter_name: Path} for all dirs in examples/.""" - return {d.name: d for d in EXAMPLES_DIR.iterdir() if d.is_dir()} - - -def get_test_chapters(): - """Return a set of real chapter names that have test files.""" - chapters = set() - for test_file in TESTS_DIR.glob("test_ch*.py"): - m = re.match(r"test_(ch\d+)\.py", test_file.name) - if m: - prefix = m.group(1) - match = next( - ( - d.name - for d in EXAMPLES_DIR.iterdir() - if d.name.startswith(prefix) - ), - None, - ) - if match: - chapters.add(match) - return chapters - - -def test_chapters_have_valid_names_and_tests(): - """Check that all example chapters are named properly and have - tests.""" - example_chapters = get_example_chapters() - test_chapters = get_test_chapters() - errors = [] - # 1. Fail if any dir does not follow "ch" + number convention - bad_names = [ - name for name in example_chapters if not re.match(r"^ch\d+", name) - ] - if bad_names: - errors.append( - "Invalid example chapter names (must match '^ch[0-9]+'): " - + str(bad_names) - ) - # 2. Fail if any example chapter has no corresponding test - missing_tests = set(example_chapters) - test_chapters - if missing_tests: - errors.append( - "Missing test files for chapters or bad test file name " - "(must be named 'test_ch.py'): " + str(sorted(missing_tests)) - ) - assert not errors, "\n".join(errors) From b2fd64a404d17b978c059cbc14ea88238054b854 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Fri, 19 Sep 2025 17:19:53 -0400 Subject: [PATCH 18/19] revert gitignore --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.gitignore b/.gitignore index 6fb753f..099e294 100644 --- a/.gitignore +++ b/.gitignore @@ -6,11 +6,6 @@ __pycache__/ # C extensions *.so -# ignore any data saved by the diffpy.cmi examples -docs/examples/ch*/solutions/diffpy-cmi/fig/* -docs/examples/ch*/solutions/diffpy-cmi/fit/* -docs/examples/ch*/solutions/diffpy-cmi/res/* - # Distribution / packaging .Python env/ From 666707d4a54de75af123cbf8ad2bf863c0f98c5c Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Sun, 21 Sep 2025 17:12:56 -0400 Subject: [PATCH 19/19] tell covecov to ignore tests, pycache, and temp dir --- pyproject.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 55c71c2..eb745d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,13 @@ include = ["*"] # package names should match these glob patterns (["*"] by defa exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) +[tool.coverage.run] +omit = [ + "*/tests/*", + "*/examples_copy*/*", + "*/__pycache__/*", +] + [project.scripts] cmi = "diffpy.cmi.cli:main"