From ba384ae1ad9c77ae978656b4fbf636f293f29a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Wed, 11 Oct 2023 07:56:53 +0200 Subject: [PATCH 1/2] TST: migrate from nosetest to pytest --- .circleci/config.yml | 98 +----------- nose.cfg | 4 - pyproject.toml | 1 - requirements/tests.txt | 3 - .../light_cone/tests/test_light_cone.py | 139 ++++++++---------- .../halo_catalog/analysis_operators.py | 6 + .../halo_analysis/tests/test_halo_catalog.py | 100 ++++++------- .../halo_analysis/tests/test_halo_finders.py | 108 ++++++++++---- .../tests/test_halo_finders_ts.py | 64 ++++---- .../tests/test_radmc3d_exporter.py | 99 ++++--------- yt_astro_analysis/utilities/testing.py | 44 +++--- 11 files changed, 283 insertions(+), 383 deletions(-) delete mode 100644 nose.cfg diff --git a/.circleci/config.yml b/.circleci/config.yml index 0fbbd3ce..b5d98335 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,13 +7,9 @@ commands: - run: name: "Set environment variables." command: | - echo 'export GOLD_STANDARD=HEAD' >> $BASH_ENV echo 'export ROCKSTAR_DIR=$HOME/rockstar-galaxies' >> $BASH_ENV echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROCKSTAR_DIR' >> $BASH_ENV echo 'export YT_DATA=$HOME/yt_test' >> $BASH_ENV - echo 'export TEST_DIR=$HOME/test_results' >> $BASH_ENV - echo 'export TEST_NAME=astro_analysis' >> $BASH_ENV - echo 'export TEST_FLAGS="--nologcapture -v --with-answer-testing --local --local-dir $TEST_DIR --answer-name=$TEST_NAME --answer-big-data"' >> $BASH_ENV install-with-yt-dev: description: "Install dependencies with yt from source." @@ -21,6 +17,7 @@ commands: - run: name: "Install dependencies with yt from source." command: | + set -x source $BASH_ENV sudo apt update sudo apt upgrade @@ -75,30 +72,7 @@ commands: girder-cli --api-url https://girder.hub.yt/api/v1 download 577c09480d7c6b0001ad5be2 $YT_DATA/enzo_tiny_cosmology fi - build-and-test-nose: - description: "Build yt_astro_analysis and run tests (nose)." - steps: - - run: - name: "Build yt_astro_analysis and run tests." - command: | - # tag the tip so we can get back there - git tag tip - source $BASH_ENV - source $HOME/venv/bin/activate - # generate answers if not cached - if [ ! -f ${TEST_DIR}/${TEST_NAME}/${TEST_NAME} ]; then - git checkout $GOLD_STANDARD - python -m pip install -e . - nosetests $TEST_FLAGS --answer-store - fi - # return to tip and run comparison tests - git checkout tip - python -m pip install -e . - coverage run `which nosetests` $TEST_FLAGS - # code coverage report - codecov - - build-and-test-pytest: + build-and-test: description: "Build yt_astro_analysis and run tests (pytest)." steps: - run: @@ -107,7 +81,7 @@ commands: source $BASH_ENV source $HOME/venv/bin/activate python -m pip install -e . - pytest + pytest --color=yes -ra -s build-docs: description: "Test the docs build." @@ -142,62 +116,6 @@ jobs: working_directory: ~/yt_astro_analysis - steps: - - checkout - - set-env - - - restore_cache: - name: "Restore dependencies cache." - key: python-<< parameters.tag >>-dependencies-bonxie - - - install-with-yt-dev - - - save_cache: - name: "Save dependencies cache" - key: python-<< parameters.tag >>-dependencies-bonxie - paths: - - ~/.cache/pip - - ~/venv - - ~/yt-git - - ~/rockstar-galaxies - - - restore_cache: - name: "Restore test data cache." - key: test-data-bonxie - - - download-test-data - - - save_cache: - name: "Save test data cache." - key: test-data-bonxie - paths: - - ~/yt_test - - - restore_cache: - name: "Restore test answers." - key: python-<< parameters.tag >>-test-answers-bonxie - - - build-and-test-nose - - - save_cache: - name: "Save test answers cache." - key: python-<< parameters.tag >>-test-answers-bonxie - paths: - - ~/test_results - - run-tests-pytest: - # Run a subset of the test suite (yield-based tests are not supported by pytest) - # this is necessary to test in Python 3.10+ because it's not compatible with nose - parameters: - tag: - type: string - default: latest - executor: - name: python - tag: << parameters.tag >> - - working_directory: ~/yt_astro_analysis - steps: - checkout - set-env @@ -233,7 +151,7 @@ jobs: name: "Restore test answers." key: python-<< parameters.tag >>-test-answers-bonxie-pytest - - build-and-test-pytest + - build-and-test - save_cache: name: "Save test answers cache." @@ -282,11 +200,7 @@ workflows: name: "Python 3.9 tests" tag: "3.9" - - run-tests-pytest: - name: "Python 3.10 tests" - tag: "3.10" - - - run-tests-pytest: + - run-tests: name: "Python 3.11 tests" tag: "3.11" @@ -307,7 +221,7 @@ workflows: name: "Python 3.9 tests" tag: "3.9" - - run-tests-pytest: + - run-tests: name: "Python 3.11 tests" tag: "3.11" diff --git a/nose.cfg b/nose.cfg deleted file mode 100644 index 9ae7854c..00000000 --- a/nose.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[nosetests] -detailed-errors = 1 -exclude = answer_testing -with-xunit = 1 diff --git a/pyproject.toml b/pyproject.toml index 5b2372f7..00b10b58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,6 @@ select = [ combine-as-imports = true known-third-party = [ "IPython", - "nose", "numpy", "sympy", "matplotlib", diff --git a/requirements/tests.txt b/requirements/tests.txt index e3dfe645..6cc1c146 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -3,8 +3,5 @@ astropy scipy # test dependencies -nose -nose-timer pytest girder-client -codecov diff --git a/yt_astro_analysis/cosmological_observation/light_cone/tests/test_light_cone.py b/yt_astro_analysis/cosmological_observation/light_cone/tests/test_light_cone.py index cada2c37..1f28e935 100644 --- a/yt_astro_analysis/cosmological_observation/light_cone/tests/test_light_cone.py +++ b/yt_astro_analysis/cosmological_observation/light_cone/tests/test_light_cone.py @@ -14,96 +14,79 @@ # ----------------------------------------------------------------------------- import os -import shutil -import tempfile +import h5py import numpy as np +import numpy.testing as npt +import pytest +import unyt as un -from yt.testing import assert_equal -from yt.units.yt_array import YTQuantity -from yt.utilities.answer_testing.framework import AnswerTestingTest -from yt.utilities.on_demand_imports import _h5py as h5py +import yt # noqa +from yt.testing import requires_file from yt_astro_analysis.cosmological_observation.api import LightCone -from yt_astro_analysis.utilities.testing import requires_sim ETC = "enzo_tiny_cosmology/32Mpc_32.enzo" _funits = { - "density": YTQuantity(1, "g/cm**3"), - "temperature": YTQuantity(1, "K"), - "length": YTQuantity(1, "cm"), + "density": un.unyt_quantity(1, "g/cm**3"), + "temperature": un.unyt_quantity(1, "K"), + "length": un.unyt_quantity(1, "cm"), } -class LightConeProjectionTest(AnswerTestingTest): - _type_name = "LightConeProjection" - _attrs = () - - def __init__(self, parameter_file, simulation_type, field, weight_field=None): - self.parameter_file = parameter_file - self.simulation_type = simulation_type - self.ds = os.path.basename(self.parameter_file) - self.field = field - self.weight_field = weight_field - - @property - def storage_name(self): - return "_".join( - (os.path.basename(self.parameter_file), self.field, str(self.weight_field)) - ) - - def run(self): - # Set up in a temp dir - tmpdir = tempfile.mkdtemp() - curdir = os.getcwd() - os.chdir(tmpdir) - - lc = LightCone( - self.parameter_file, - self.simulation_type, - 0.0, - 0.1, - observer_redshift=0.0, - time_data=False, - ) - lc.calculate_light_cone_solution(seed=123456789, filename="LC/solution.txt") - lc.project_light_cone( - (600.0, "arcmin"), - (60.0, "arcsec"), - self.field, - weight_field=self.weight_field, - save_stack=True, - ) - - dname = f"{self.field}_{self.weight_field}" - fh = h5py.File("LC/LightCone.h5", mode="r") +@requires_file(ETC) +@pytest.mark.parametrize( + "field, weight_field, expected", + [ + ( + "density", + None, + [6.0000463633868075e-05, 1.1336502301470154e-05, 0.08970763360935877], + ), + ( + "temperature", + "density", + [37.79481498628398, 0.018410545597485613, 543702.4613479003], + ), + ], +) +def test_light_cone_projection(tmp_path, field, weight_field, expected): + parameter_file = ETC + simulation_type = "Enzo" + field = field + weight_field = weight_field + + os.chdir(tmp_path) + lc = LightCone( + parameter_file, + simulation_type, + near_redshift=0.0, + far_redshift=0.1, + observer_redshift=0.0, + time_data=False, + ) + lc.calculate_light_cone_solution(seed=123456789, filename="LC/solution.txt") + lc.project_light_cone( + (600.0, "arcmin"), + (60.0, "arcsec"), + field, + weight_field=weight_field, + save_stack=True, + ) + + dname = f"{field}_{weight_field}" + with h5py.File("LC/LightCone.h5", mode="r") as fh: data = fh[dname][()] units = fh[dname].attrs["units"] - if self.weight_field is None: - punits = _funits[self.field] * _funits["length"] + if weight_field is None: + punits = _funits[field] * _funits["length"] else: - punits = ( - _funits[self.field] * _funits[self.weight_field] * _funits["length"] - ) - wunits = fh["weight_field_%s" % self.weight_field].attrs["units"] - pwunits = _funits[self.weight_field] * _funits["length"] + punits = _funits[field] * _funits[weight_field] * _funits["length"] + wunits = fh[f"weight_field_{weight_field}"].attrs["units"] + pwunits = _funits[weight_field] * _funits["length"] assert wunits == str(pwunits.units) - assert units == str(punits.units) - fh.close() - - # clean up - os.chdir(curdir) - shutil.rmtree(tmpdir) - - mean = data.mean() - mi = data[data.nonzero()].min() - ma = data.max() - return np.array([mean, mi, ma]) - - def compare(self, new_result, old_result): - assert_equal(new_result, old_result, verbose=True) - + assert units == str(punits.units) -@requires_sim(ETC, "Enzo") -def test_light_cone_projection(): - yield LightConeProjectionTest(ETC, "Enzo", "density") - yield LightConeProjectionTest(ETC, "Enzo", "temperature", weight_field="density") + mean = np.nanmean(data) + mi = np.nanmin(data[data.nonzero()]) + ma = np.nanmax(data) + npt.assert_equal([mean, mi, ma], expected, verbose=True) diff --git a/yt_astro_analysis/halo_analysis/halo_catalog/analysis_operators.py b/yt_astro_analysis/halo_analysis/halo_catalog/analysis_operators.py index 857a4223..c2888115 100644 --- a/yt_astro_analysis/halo_analysis/halo_catalog/analysis_operators.py +++ b/yt_astro_analysis/halo_analysis/halo_catalog/analysis_operators.py @@ -63,6 +63,12 @@ def add_quantity(name, function): quantity_registry[name] = AnalysisQuantity(function) +def _remove_quantity(name): + # this is useful to avoid test pollution when using add_quantity in tests + # but it's not meant as public API + quantity_registry.pop(name) + + class AnalysisQuantity(AnalysisCallback): r""" An AnalysisQuantity is a function that takes minimally a target object, diff --git a/yt_astro_analysis/halo_analysis/tests/test_halo_catalog.py b/yt_astro_analysis/halo_analysis/tests/test_halo_catalog.py index 79bfd549..3e04ee23 100644 --- a/yt_astro_analysis/halo_analysis/tests/test_halo_catalog.py +++ b/yt_astro_analysis/halo_analysis/tests/test_halo_catalog.py @@ -13,73 +13,57 @@ # The full license is in the file COPYING.txt, distributed with this software. # ----------------------------------------------------------------------------- -import os -import shutil -import tempfile - -import numpy as np +import numpy.testing as npt +import pytest +import unyt as un from yt.loaders import load -from yt.testing import assert_equal -from yt.utilities.answer_testing.framework import ( - AnswerTestingTest, - data_dir_load, - requires_ds, +from yt.testing import requires_file +from yt_astro_analysis.halo_analysis import HaloCatalog +from yt_astro_analysis.halo_analysis.halo_catalog.analysis_operators import ( + _remove_quantity, + add_quantity, ) -from yt_astro_analysis.halo_analysis import HaloCatalog, add_quantity - - -def _nstars(halo): - sp = halo.data_object - return (sp["all", "creation_time"] > 0).sum() - - -add_quantity("nstars", _nstars) - - -class HaloQuantityTest(AnswerTestingTest): - _type_name = "HaloQuantity" - _attrs = () - - def __init__(self, data_ds_fn, halos_ds_fn): - self.data_ds_fn = data_ds_fn - self.halos_ds_fn = halos_ds_fn - self.ds = data_dir_load(data_ds_fn) - - def run(self): - curdir = os.getcwd() - tmpdir = tempfile.mkdtemp() - os.chdir(tmpdir) - - dds = data_dir_load(self.data_ds_fn) - hds = data_dir_load(self.halos_ds_fn) - hc = HaloCatalog( - data_ds=dds, halos_ds=hds, output_dir=os.path.join(tmpdir, str(dds)) - ) - hc.add_callback("sphere") - hc.add_quantity("nstars") - hc.create() - - fn = os.path.join(tmpdir, str(dds), "%s.0.h5" % str(dds)) - ds = load(fn) - ad = ds.all_data() - mi, ma = ad.quantities.extrema("nstars") - mean = ad.quantities.weighted_average_quantity("nstars", "particle_ones") +from yt_astro_analysis.utilities.testing import data_dir_load - os.chdir(curdir) - shutil.rmtree(tmpdir) - return np.array([mean, mi, ma]) +@pytest.fixture +def nstars_defined(): + def _nstars(halo): + sp = halo.data_object + return (sp["all", "creation_time"] > 0).sum() - def compare(self, new_result, old_result): - assert_equal(new_result, old_result, verbose=True) + add_quantity("nstars", _nstars) + yield + _remove_quantity("nstars") rh0 = "rockstar_halos/halos_0.0.bin" e64 = "Enzo_64/DD0043/data0043" -@requires_ds(rh0) -@requires_ds(e64) -def test_halo_quantity(): - yield HaloQuantityTest(e64, rh0) +@requires_file(rh0) +@requires_file(e64) +@pytest.mark.usefixtures("nstars_defined") +def test_halo_quantity(tmp_path): + data_ds_fn = e64 + halos_ds_fn = rh0 + ds = data_dir_load(data_ds_fn) + + dds = data_dir_load(data_ds_fn) + hds = data_dir_load(halos_ds_fn) + hc = HaloCatalog(data_ds=dds, halos_ds=hds, output_dir=str(tmp_path)) + hc.add_callback("sphere") + hc.add_quantity("nstars") + hc.create() + + fn = tmp_path / str(dds) / f"{dds}.0.h5" + ds = load(fn) + ad = ds.all_data() + mi, ma = ad.quantities.extrema("nstars") + mean = ad.quantities.weighted_average_quantity("nstars", "particle_ones") + + npt.assert_equal( + un.unyt_array([mean, mi, ma]), + [28.533783783783782, 0.0, 628.0] * un.dimensionless, + ) diff --git a/yt_astro_analysis/halo_analysis/tests/test_halo_finders.py b/yt_astro_analysis/halo_analysis/tests/test_halo_finders.py index 448644dd..4920415e 100644 --- a/yt_astro_analysis/halo_analysis/tests/test_halo_finders.py +++ b/yt_astro_analysis/halo_analysis/tests/test_halo_finders.py @@ -1,19 +1,70 @@ import os -import shutil import sys -import tempfile + +import pytest from yt.frontends.halo_catalog.data_structures import YTHaloCatalogDataset from yt.frontends.rockstar.data_structures import RockstarDataset from yt.loaders import load -from yt.utilities.answer_testing.framework import FieldValuesTest, requires_ds +from yt.testing import assert_allclose_units, requires_file + +_expected = { + "fof": { + ("halos", "particle_position_x"): ( + [0.5395228362545148, 0.009322612122839679, 0.9851068812956318], + "unitary", + ), + ("halos", "particle_position_y"): ( + [0.5622196217878699, 0.003041142049505119, 0.9959844650128505], + "unitary", + ), + ("halos", "particle_position_z"): ( + [0.5203774727457908, 0.0007079871575761398, 0.9990256781592326], + "unitary", + ), + ("halos", "particle_mass"): ( + [5874503393478.599, 719586865542.6366, 46593249543885.67], + "Msun", + ), + }, + "hop": { + ("halos", "particle_position_x"): ( + [0.5541340411242247, 0.054537564738846515, 0.9874987118674894], + "unitary", + ), + ("halos", "particle_position_y"): ( + [0.5230348494537058, 0.02430675276218758, 0.9654696366375637], + "unitary", + ), + ("halos", "particle_position_z"): ( + [0.4661933399047146, 0.0049056915980563165, 0.9985715364094563], + "unitary", + ), + ("halos", "particle_mass"): ( + [13744109131864.344, 4407469551448.649, 38677794022916.664], + "Msun", + ), + }, + "rockstar": { + ("halos", "particle_position_x"): ( + [16.706823560058094, 1.2783197164535522, 31.622468948364258], + "Mpccm/h", + ), + ("halos", "particle_position_y"): ( + [16.977042329115946, 0.745559811592102, 31.82260513305664], + "Mpccm/h", + ), + ("halos", "particle_position_z"): ( + [15.993848544652344, 0.15180185437202454, 31.95146942138672], + "Mpccm/h", + ), + ("halos", "particle_mass"): ( + [5220228205450.492, 126287495168.0, 27593818505216.0], + "Msun/h", + ), + }, +} -_fields = ( - ("halos", "particle_position_x"), - ("halos", "particle_position_y"), - ("halos", "particle_position_z"), - ("halos", "particle_mass"), -) methods = {"fof": 2, "hop": 2, "rockstar": 3} decimals = {"fof": 10, "hop": 10, "rockstar": 1} @@ -21,18 +72,19 @@ etiny = "enzo_tiny_cosmology/DD0046/DD0046" -@requires_ds(etiny, big_data=True) -def test_halo_finders_single(): +@requires_file(etiny) +def test_halo_finders_single(tmp_path): + pytest.importorskip("mpi4py") from mpi4py import MPI - tmpdir = tempfile.mkdtemp() - curdir = os.getcwd() - os.chdir(tmpdir) + os.chdir(tmp_path) filename = os.path.join(os.path.dirname(__file__), "run_halo_finder.py") for method in methods: comm = MPI.COMM_SELF.Spawn( - sys.executable, args=[filename, method, tmpdir], maxprocs=methods[method] + sys.executable, + args=[filename, method, str(tmp_path)], + maxprocs=methods[method], ) comm.Disconnect() @@ -40,9 +92,8 @@ def test_halo_finders_single(): hcfn = "halos_0.0.bin" else: hcfn = os.path.join("DD0046", "DD0046.0.h5") - fn = os.path.join(tmpdir, "halo_catalogs", method, hcfn) - ds = load(fn) + ds = load(tmp_path / "halo_catalogs" / method / hcfn) if method == "rockstar": ds.parameters["format_revision"] = 2 ds_type = RockstarDataset @@ -50,12 +101,19 @@ def test_halo_finders_single(): ds_type = YTHaloCatalogDataset assert isinstance(ds, ds_type) - for field in _fields: - my_test = FieldValuesTest( - ds, field, particle_type=True, decimals=decimals[method] - ) - my_test.suffix = method - yield my_test + expected_results = _expected[method] + for field, expected in expected_results.items(): + obj = ds.all_data() + field = obj._determine_fields(field)[0] + weight_field = (field[0], "particle_ones") + avg = obj.quantities.weighted_average_quantity(field, weight=weight_field) + mi, ma = obj.quantities.extrema(field) - os.chdir(curdir) - shutil.rmtree(tmpdir) + expected_value, expected_units = expected + assert_allclose_units( + [avg, mi, ma], + ds.arr(expected_value, expected_units), + 10.0 ** (-decimals[method]), + err_msg=f"Field values for {field} not equal.", + verbose=True, + ) diff --git a/yt_astro_analysis/halo_analysis/tests/test_halo_finders_ts.py b/yt_astro_analysis/halo_analysis/tests/test_halo_finders_ts.py index c92a6331..f972726c 100644 --- a/yt_astro_analysis/halo_analysis/tests/test_halo_finders_ts.py +++ b/yt_astro_analysis/halo_analysis/tests/test_halo_finders_ts.py @@ -1,17 +1,12 @@ import os import sys +import pytest + +import yt from yt.frontends.halo_catalog.data_structures import YTHaloCatalogDataset from yt.frontends.rockstar.data_structures import RockstarDataset -from yt.loaders import load -from yt_astro_analysis.utilities.testing import TempDirTest - -_fields = ( - ("halos", "particle_position_x"), - ("halos", "particle_position_y"), - ("halos", "particle_position_z"), - ("halos", "particle_mass"), -) +from yt.testing import requires_file methods = {"fof": 2, "hop": 2, "rockstar": 3} decimals = {"fof": 10, "hop": 10, "rockstar": 1} @@ -19,33 +14,32 @@ etiny = "enzo_tiny_cosmology/32Mpc_32.enzo" -class HaloFinderTimeSeriesTest(TempDirTest): - def test_halo_finders(self): - from mpi4py import MPI +@requires_file(etiny) +def test_halo_finders(tmp_path): + pytest.importorskip("mpi4py") + from mpi4py import MPI + + os.chdir(tmp_path) + + filename = os.path.join(os.path.dirname(__file__), "run_halo_finder_ts.py") + for method in methods: + comm = MPI.COMM_SELF.Spawn( + sys.executable, + args=[filename, method, str(tmp_path)], + maxprocs=methods[method], + ) + comm.Disconnect() - filename = os.path.join(os.path.dirname(__file__), "run_halo_finder_ts.py") - for method in methods: - comm = MPI.COMM_SELF.Spawn( - sys.executable, - args=[filename, method, self.tmpdir], - maxprocs=methods[method], - ) - comm.Disconnect() + if method == "rockstar": + hcfns = [f"halos_{i}.0.bin" for i in range(2)] + else: + hcfns = [os.path.join(f"DD{i:04d}", f"DD{i:04d}.0.h5") for i in [20, 46]] + for hcfn in hcfns: + ds = yt.load(tmp_path / "halo_catalogs" / method / hcfn) if method == "rockstar": - hcfns = [f"halos_{i}.0.bin" for i in range(2)] + ds.parameters["format_revision"] = 2 + ds_type = RockstarDataset else: - hcfns = [ - os.path.join(f"DD{i:04d}", f"DD{i:04d}.0.h5") for i in [20, 46] - ] - - for hcfn in hcfns: - fn = os.path.join(self.tmpdir, "halo_catalogs", method, hcfn) - - ds = load(fn) - if method == "rockstar": - ds.parameters["format_revision"] = 2 - ds_type = RockstarDataset - else: - ds_type = YTHaloCatalogDataset - assert isinstance(ds, ds_type) + ds_type = YTHaloCatalogDataset + assert isinstance(ds, ds_type) diff --git a/yt_astro_analysis/radmc3d_export/tests/test_radmc3d_exporter.py b/yt_astro_analysis/radmc3d_export/tests/test_radmc3d_exporter.py index ef3291d0..3398799e 100644 --- a/yt_astro_analysis/radmc3d_export/tests/test_radmc3d_exporter.py +++ b/yt_astro_analysis/radmc3d_export/tests/test_radmc3d_exporter.py @@ -11,85 +11,28 @@ # ----------------------------------------------------------------------------- import os -import shutil -import tempfile import numpy as np +import numpy.testing as npt -import yt -from yt.testing import assert_allclose -from yt.utilities.answer_testing.framework import AnswerTestingTest, requires_ds +from yt.testing import requires_file from yt_astro_analysis.radmc3d_export.api import RadMC3DWriter - - -class RadMC3DValuesTest(AnswerTestingTest): - """ - - This test writes out a "dust_density.inp" file, - reads it back in, and checks the sum of the - values for degradation. - - """ - - _type_name = "RadMC3DValuesTest" - _attrs = ("field",) - - def __init__(self, ds_fn, field, decimals=10): - super().__init__(ds_fn) - self.field = field - self.decimals = decimals - - def run(self): - # Set up in a temp dir - tmpdir = tempfile.mkdtemp() - curdir = os.getcwd() - os.chdir(tmpdir) - - # try to write the output files - writer = RadMC3DWriter(self.ds) - writer.write_amr_grid() - writer.write_dust_file(self.field, "dust_density.inp") - - # compute the sum of the values in the resulting file - total = 0.0 - with open("dust_density.inp") as f: - for i, line in enumerate(f): - # skip header - if i < 3: - continue - - line = line.rstrip() - total += np.float64(line) - - # clean up - os.chdir(curdir) - shutil.rmtree(tmpdir) - - return total - - def compare(self, new_result, old_result): - err_msg = f"Total value for {self.field} not equal." - assert_allclose( - new_result, - old_result, - 10.0 ** (-self.decimals), - err_msg=err_msg, - verbose=True, - ) - +from yt_astro_analysis.utilities.testing import data_dir_load etiny = "enzo_tiny_cosmology/DD0046/DD0046" -@requires_ds(etiny) -def test_radmc3d_exporter_continuum(): +@requires_file(etiny) +def test_radmc3d_exporter_continuum(tmp_path): """ This test is simply following the description in the docs for how to generate the necessary output files to run a continuum emission map from dust for one of our sample datasets. """ + os.chdir(tmp_path) - ds = yt.load(etiny) + field = ("gas", "dust_density") + ds = data_dir_load(etiny) # Make up a dust density field where dust density is 1% of gas density dust_to_gas = 0.01 @@ -98,10 +41,32 @@ def _DustDensity(field, data): return dust_to_gas * data["density"] ds.add_field( - ("gas", "dust_density"), + field, function=_DustDensity, sampling_type="cell", units="g/cm**3", ) - yield RadMC3DValuesTest(ds, ("gas", "dust_density")) + # try to write the output files + writer = RadMC3DWriter(ds) + writer.write_amr_grid() + writer.write_dust_file(field, "dust_density.inp") + + # compute the sum of the values in the resulting file + total = 0.0 + with open("dust_density.inp") as f: + for i, line in enumerate(f): + # skip header + if i < 3: + continue + + line = line.rstrip() + total += np.float64(line) + + npt.assert_allclose( + total, + 4.240471916352974e-27, + rtol=1e-10, + err_msg=f"Total value for {field} not equal.", + verbose=True, + ) diff --git a/yt_astro_analysis/utilities/testing.py b/yt_astro_analysis/utilities/testing.py index a6ae87a1..517db8ab 100644 --- a/yt_astro_analysis/utilities/testing.py +++ b/yt_astro_analysis/utilities/testing.py @@ -14,33 +14,26 @@ # ----------------------------------------------------------------------------- import os -import shutil -import tempfile -from unittest import TestCase +import warnings +import yt from yt.config import ytcfg -from yt.data_objects.time_series import SimulationTimeSeries -from yt.loaders import load_simulation -from yt.utilities.answer_testing.framework import AnswerTestingTest -class TempDirTest(TestCase): - """ - A test class that runs in a temporary directory and - removes it afterward. - """ - - def setUp(self): - self.curdir = os.getcwd() - self.tmpdir = tempfile.mkdtemp() - os.chdir(self.tmpdir) - - def tearDown(self): - os.chdir(self.curdir) - shutil.rmtree(self.tmpdir) +def data_dir_load(fn, *args, **kwargs): + # wrap yt.load but only load from test_data_dir + path = os.path.join(ytcfg.get("yt", "test_data_dir"), fn) + return yt.load(path, *args, **kwargs) def requires_sim(sim_fn, sim_type, file_check=False): + warnings.warn( + "yt_astro_analysis.utilities.testing.requires_sim " + "is deprecated and will be removed in a future version. " + "Please consider implementing your own solution.", + DeprecationWarning, + stacklevel=2, + ) from functools import wraps from nose import SkipTest @@ -62,6 +55,17 @@ def ftrue(func): def can_run_sim(sim_fn, sim_type, file_check=False): + warnings.warn( + "yt_astro_analysis.utilities.testing.can_run_sim " + "is deprecated and will be removed in a future version. " + "Please consider implementing your own solution.", + DeprecationWarning, + stacklevel=2, + ) + from yt.data_objects.time_series import SimulationTimeSeries + from yt.loaders import load_simulation + from yt.utilities.answer_testing.framework import AnswerTestingTest + result_storage = AnswerTestingTest.result_storage if isinstance(sim_fn, SimulationTimeSeries): return result_storage is not None From 1c73485a60e10de920746a7ba309d29c3f199f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Sat, 30 Sep 2023 18:50:17 +0200 Subject: [PATCH 2/2] TST: test on GHA --- .circleci/config.yml | 230 ------------------------------ .github/workflows/build-test.yaml | 89 ++++++++++++ 2 files changed, 89 insertions(+), 230 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/build-test.yaml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index b5d98335..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,230 +0,0 @@ -version: 2.1 - -commands: - set-env: - description: "Set environment variables." - steps: - - run: - name: "Set environment variables." - command: | - echo 'export ROCKSTAR_DIR=$HOME/rockstar-galaxies' >> $BASH_ENV - echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROCKSTAR_DIR' >> $BASH_ENV - echo 'export YT_DATA=$HOME/yt_test' >> $BASH_ENV - - install-with-yt-dev: - description: "Install dependencies with yt from source." - steps: - - run: - name: "Install dependencies with yt from source." - command: | - set -x - source $BASH_ENV - sudo apt update - sudo apt upgrade - sudo apt install -y dvipng texlive-latex-extra - sudo apt install -y libopenmpi-dev openmpi-bin gcc-9 - python -m venv $HOME/venv - source $HOME/venv/bin/activate - python -m pip install --upgrade pip - pip install mpi4py - export MAX_BUILD_CORES=2 - python -m pip install git+https://github.com/yt-project/yt - - export YT_ASTRO_DIR=$(pwd) - # install rockstar - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 - if [ ! -f $ROCKSTAR_DIR/VERSION ]; then - git clone https://bitbucket.org/pbehroozi/rockstar-galaxies $ROCKSTAR_DIR - pushd $ROCKSTAR_DIR - - # apply patches necessary for compat with Ubuntu 22.04 - git apply $YT_ASTRO_DIR/rockstar-patches/cflags.patch --whitespace=fix - - make lib - popd - fi - echo $ROCKSTAR_DIR > rockstar.cfg - - # never attempt to build h5py from source - python -m pip install h5py --only-binary h5py - - # install yt_astro_analysis with extra dev dependencies - python -m pip install . - python -m pip install --requirement requirements/tests.txt - - # configure yt - mkdir -p $HOME/.config/yt # avoid a warning from yt - yt config set --global yt suppress_stream_logging True - yt config set --global yt test_data_dir $YT_DATA - yt config set --global yt log_level 30 - - download-test-data: - description: "Download test data." - steps: - - run: - name: "Download test data." - no_output_timeout: 20m - command: | - if [ ! -f $YT_DATA/enzo_tiny_cosmology/DD0046/DD0046 ]; then - source $BASH_ENV - source $HOME/venv/bin/activate - mkdir -p $YT_DATA - girder-cli --api-url https://girder.hub.yt/api/v1 download 577c09480d7c6b0001ad5be2 $YT_DATA/enzo_tiny_cosmology - fi - - build-and-test: - description: "Build yt_astro_analysis and run tests (pytest)." - steps: - - run: - name: "Build yt_astro_analysis and run tests." - command: | - source $BASH_ENV - source $HOME/venv/bin/activate - python -m pip install -e . - pytest --color=yes -ra -s - - build-docs: - description: "Test the docs build." - steps: - - run: - name: "Test the docs build." - command: | - source $HOME/venv/bin/activate - python -m pip install --upgrade pip - python -m pip install --requirement requirements/docs.txt - cd doc/source - python -m sphinx -M html "." "_build" -W - -executors: - python: - parameters: - tag: - type: string - default: latest - docker: - - image: cimg/python:<< parameters.tag >> - -jobs: - run-tests: - parameters: - tag: - type: string - default: latest - executor: - name: python - tag: << parameters.tag >> - - working_directory: ~/yt_astro_analysis - - steps: - - checkout - - set-env - - - restore_cache: - name: "Restore dependencies cache." - key: python-<< parameters.tag >>-dependencies-bonxie - - - install-with-yt-dev - - - save_cache: - name: "Save dependencies cache" - key: python-<< parameters.tag >>-dependencies-bonxie - paths: - - ~/.cache/pip - - ~/venv - - ~/yt-git - - ~/rockstar-galaxies - - - restore_cache: - name: "Restore test data cache." - key: test-data-bonxie - - - download-test-data - - - save_cache: - name: "Save test data cache." - key: test-data-bonxie - paths: - - ~/yt_test - - - restore_cache: - name: "Restore test answers." - key: python-<< parameters.tag >>-test-answers-bonxie-pytest - - - build-and-test - - - save_cache: - name: "Save test answers cache." - key: python-<< parameters.tag >>-test-answers-bonxie-pytest - paths: - - ~/test_results - - docs-test: - parameters: - tag: - type: string - default: latest - executor: - name: python - tag: << parameters.tag >> - - working_directory: ~/yt_astro_analysis - - steps: - - checkout - - set-env - - - restore_cache: - name: "Restore dependencies cache." - key: python-<< parameters.tag >>-dependencies-bonxie - - - install-with-yt-dev - - - save_cache: - name: "Save dependencies cache" - key: python-<< parameters.tag >>-dependencies-bonxie - paths: - - ~/.cache/pip - - ~/venv - - ~/yt-git - - ~/rockstar-galaxies - - - build-docs - -workflows: - version: 2 - - normal-tests: - jobs: - - run-tests: - name: "Python 3.9 tests" - tag: "3.9" - - - run-tests: - name: "Python 3.11 tests" - tag: "3.11" - - - docs-test: - name: "Test docs build" - tag: "3.9" - - weekly: - triggers: - - schedule: - cron: "0 0 * * 2" - filters: - branches: - only: - - main - jobs: - - run-tests: - name: "Python 3.9 tests" - tag: "3.9" - - - run-tests: - name: "Python 3.11 tests" - tag: "3.11" - - - docs-test: - name: "Test docs build" - tag: "3.9" diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml new file mode 100644 index 00000000..1e5b88fb --- /dev/null +++ b/.github/workflows/build-test.yaml @@ -0,0 +1,89 @@ +name: Build and Test + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + schedule: + # run this every Wednesday at 3 am UTC + - cron: 0 3 * * 3 + +jobs: + + get_yt_data: + runs-on: ubuntu-latest + steps: + - name: Fetch yt data + run: | + pipx run girder-client \ + --api-url https://girder.hub.yt/api/v1 \ + download 577c09480d7c6b0001ad5be2 enzo_tiny_cosmology + + - run: ls enzo_tiny_cosmology + + - uses: actions/upload-artifact@v3 + with: + name: enzo_tiny_cosmology + path: enzo_tiny_cosmology + + build: + name: "py${{ matrix.python-version }} on ${{ matrix.os }}" + needs: + - get_yt_data + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + python-version: + - '3.9' + - '3.10' + - '3.11' + + runs-on: ${{ matrix.os }} + + concurrency: + # auto-cancel any in-progress job *on the same branch* + group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.tests-type }}-py${{ matrix.python-version }}-${{ matrix.os }}-${{ matrix.test-runner }} + cancel-in-progress: true + + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up Conda + uses: s-weigand/setup-conda@v1 + with: + update-conda: true + conda-channels: conda-forge + activate-conda: true + python-version: ${{matrix.python-version}} + + - name: Build + run: | + conda install rockstar-galaxies yt h5py + conda install --file=requirements/tests.txt --yes + python -m pip install -e . --no-deps + + - run: python -m pip list + + - name: Configure yt + run: | + yt config set --local yt test_data_dir $(pwd)/yt_data + yt config set --local yt suppress_stream_logging True + yt config set --local yt log_level 30 + + - run: cat yt.toml + + - uses: actions/download-artifact@v3 + with: + name: enzo_tiny_cosmology + path: yt_data/enzo_tiny_cosmology + + - run: ls yt_data/enzo_tiny_cosmology + - run: pytest --color=yes -ra