Skip to content

Commit

Permalink
TST: migrate from nosetest to pytest
Browse files Browse the repository at this point in the history
  • Loading branch information
neutrinoceros committed Oct 11, 2023
1 parent 9362472 commit 51339ba
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 383 deletions.
98 changes: 6 additions & 92 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,17 @@ 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."
steps:
- run:
name: "Install dependencies with yt from source."
command: |
set -x
source $BASH_ENV
sudo apt update
sudo apt upgrade
Expand Down Expand Up @@ -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:
Expand All @@ -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."
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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"

Expand All @@ -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"

Expand Down
4 changes: 0 additions & 4 deletions nose.cfg

This file was deleted.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ select = [
combine-as-imports = true
known-third-party = [
"IPython",
"nose",
"numpy",
"sympy",
"matplotlib",
Expand Down
3 changes: 0 additions & 3 deletions requirements/tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,5 @@ astropy
scipy

# test dependencies
nose
nose-timer
pytest
girder-client
codecov
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 51339ba

Please sign in to comment.