Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TST: migrate from nosetest to pytest #238

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 8 additions & 94 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,14 +200,10 @@ workflows:
name: "Python 3.9 tests"
tag: "3.9"

- run-tests-pytest:
- run-tests:
name: "Python 3.10 tests"
tag: "3.10"

- run-tests-pytest:
name: "Python 3.11 tests"
tag: "3.11"

- docs-test:
name: "Test docs build"
tag: "3.9"
Expand All @@ -307,9 +221,9 @@ workflows:
name: "Python 3.9 tests"
tag: "3.9"

- run-tests-pytest:
name: "Python 3.11 tests"
tag: "3.11"
- run-tests:
name: "Python 3.10 tests"
tag: "3.10"

- docs-test:
name: "Test docs build"
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 @@ -97,7 +97,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