Skip to content

Commit

Permalink
Merge pull request #106 from GeminiDRSoftware/tox
Browse files Browse the repository at this point in the history
Run tests with the installed code
  • Loading branch information
saimn committed Feb 17, 2021
2 parents d68b836 + faac9b1 commit 29a9fce
Show file tree
Hide file tree
Showing 17 changed files with 402 additions and 409 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ source =
omit =
*/conftest.py
*/*tests/*
gempy/library/config/*
*/gempy/library/config/*

[report]
show_missing = false
Expand Down
15 changes: 10 additions & 5 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ pipeline {
MPLBACKEND = "agg"
PATH = "$JENKINS_CONDA_HOME/bin:$PATH"
DRAGONS_TEST_OUT = "unit_tests_outputs/"
TOX_ARGS = "astrodata geminidr gemini_instruments gempy recipe_system"
}
steps {
echo "Running build #${env.BUILD_ID} on ${env.NODE_NAME}"
checkout scm
sh '.jenkins/scripts/setup_agent.sh'
echo "Running tests with Python 3.7"
sh 'tox -e py37-unit -v -- --basetemp=${DRAGONS_TEST_OUT} --junit-xml reports/unittests_results.xml'
sh 'tox -e py37-unit -v -- --basetemp=${DRAGONS_TEST_OUT} --junit-xml reports/unittests_results.xml ${TOX_ARGS}'
echo "Reportint coverage to CodeCov"
sh 'tox -e codecov -- -F unit'
}
Expand All @@ -84,14 +85,15 @@ pipeline {
MPLBACKEND = "agg"
PATH = "$JENKINS_CONDA_HOME/bin:$PATH"
DRAGONS_TEST_OUT = "./integ_tests_outputs/"
TOX_ARGS = "astrodata geminidr gemini_instruments gempy recipe_system"
}
steps {
echo "Running build #${env.BUILD_ID} on ${env.NODE_NAME}"
checkout scm
echo "${env.PATH}"
sh '.jenkins/scripts/setup_agent.sh'
echo "Integration tests"
sh 'tox -e py37-integ -v -- --basetemp=${DRAGONS_TEST_OUT} --junit-xml reports/integration_results.xml'
sh 'tox -e py37-integ -v -- --basetemp=${DRAGONS_TEST_OUT} --junit-xml reports/integration_results.xml ${TOX_ARGS}'
echo "Reporting coverage"
sh 'tox -e codecov -- -F integration'
} // end steps
Expand All @@ -111,14 +113,15 @@ pipeline {
MPLBACKEND = "agg"
PATH = "$JENKINS_CONDA_HOME/bin:$PATH"
DRAGONS_TEST_OUT = "regression_tests_outputs"
TOX_ARGS = "astrodata geminidr gemini_instruments gempy recipe_system"
}
steps {
echo "Running build #${env.BUILD_ID} on ${env.NODE_NAME}"
checkout scm
echo "${env.PATH}"
sh '.jenkins/scripts/setup_agent.sh'
echo "Regression tests"
sh 'tox -e py37-reg -v -- --basetemp=${DRAGONS_TEST_OUT} --junit-xml reports/regression_results.xml'
sh 'tox -e py37-reg -v -- --basetemp=${DRAGONS_TEST_OUT} --junit-xml reports/regression_results.xml ${TOX_ARGS}'
echo "Reporting coverage"
sh 'tox -e codecov -- -F regression'
} // end steps
Expand All @@ -138,13 +141,14 @@ pipeline {
MPLBACKEND = "agg"
PATH = "$JENKINS_CONDA_HOME/bin:$PATH"
DRAGONS_TEST_OUT = "gmosls_tests_outputs"
TOX_ARGS = "astrodata geminidr gemini_instruments gempy recipe_system"
}
steps {
echo "Running build #${env.BUILD_ID} on ${env.NODE_NAME}"
checkout scm
sh '.jenkins/scripts/setup_agent.sh'
echo "Running tests"
sh 'tox -e py37-gmosls -v -- --basetemp=${DRAGONS_TEST_OUT} --junit-xml reports/gmosls_results.xml'
sh 'tox -e py37-gmosls -v -- --basetemp=${DRAGONS_TEST_OUT} --junit-xml reports/gmosls_results.xml ${TOX_ARGS}'
echo "Reporting coverage"
sh 'tox -e codecov -- -F gmosls'
} // end steps
Expand All @@ -169,14 +173,15 @@ pipeline {
MPLBACKEND = "agg"
PATH = "$JENKINS_CONDA_HOME/bin:$PATH"
DRAGONS_TEST_OUT = "regression_tests_outputs"
TOX_ARGS = "astrodata geminidr gemini_instruments gempy recipe_system"
}
steps {
echo "Running build #${env.BUILD_ID} on ${env.NODE_NAME}"
checkout scm
echo "${env.PATH}"
sh '.jenkins/scripts/setup_agent.sh'
echo "Slow tests"
sh 'tox -e py37-slow -v -- --basetemp=${DRAGONS_TEST_OUT} --junit-xml reports/slow_results.xml'
sh 'tox -e py37-slow -v -- --basetemp=${DRAGONS_TEST_OUT} --junit-xml reports/slow_results.xml ${TOX_ARGS}'
echo "Reporting coverage"
sh 'tox -e codecov -- -F slow'
} // end steps
Expand Down
259 changes: 14 additions & 245 deletions astrodata/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,18 @@
"""

import os
import re
import shutil
import subprocess
import urllib
import xml.etree.ElementTree as et
from contextlib import contextmanager

import numpy as np
import pytest
from astropy.table import Table
from astropy.utils.data import download_file

URL = 'https://archive.gemini.edu/file/'


@pytest.fixture(scope="session")
def astrofaker():
"""
Wrapper fixture that prevents undesired behaviour when using astrofaker.
"""
return pytest.importorskip("astrofaker")


def assert_most_close(actual, desired, max_miss, rtol=1e-7, atol=0,
equal_nan=True, verbose=True):
"""
Expand Down Expand Up @@ -132,24 +123,6 @@ def assert_same_class(ad, ad_ref):
assert isinstance(ad, type(ad_ref))


@pytest.fixture(scope="session")
def base_temp(tmp_path_factory):
"""
Created a place to store the tests outputs. Can be set using the command line
--basetemp (WARNING: WILL DELETE ALL OF ITS CURRENT CONTENT)
Parameters
----------
tmp_path_factory : fixture
PyTest's build-in fixture.
Returns
-------
str : Path for the tests results for the current session
"""
return tmp_path_factory.mktemp("dragons-tests-")


def compare_models(model1, model2, rtol=1e-7, atol=0., check_inverse=True):
"""
Check that any two models are the same, within some tolerance on parameters
Expand Down Expand Up @@ -231,52 +204,6 @@ def compare_models(model1, model2, rtol=1e-7, atol=0., check_inverse=True):
check_inverse=False)


@pytest.fixture(scope='module')
def change_working_dir(path_to_outputs):
"""
Factory that returns the output path as a context manager object, allowing
easy access to the path to where the processed data should be stored.
Parameters
----------
path_to_outputs : pytest.fixture
Fixture containing the root path to the output files.
Returns
-------
contextmanager
Enable easy change to temporary folder when reducing data.
"""
path = os.path.join(path_to_outputs, "outputs")
os.makedirs(path, exist_ok=True)
print(f"Using working dir:\n {path}")

@contextmanager
def _change_working_dir(sub_path=""):
"""
Changed the current working directory temporarily easily using the
`with` statement.
Parameters
----------
sub_path : str
Sub-path inside the directory where we are working.
"""
oldpwd = os.getcwd()
os.chdir(path)

if sub_path:
os.makedirs(sub_path, exist_ok=True)
os.chdir(sub_path)

try:
yield
finally:
os.chdir(oldpwd)

return _change_working_dir


def download_from_archive(filename, sub_path='raw_files', env_var='DRAGONS_TEST'):
"""Download file from the archive and store it in the local cache.
Expand Down Expand Up @@ -323,185 +250,27 @@ def download_from_archive(filename, sub_path='raw_files', env_var='DRAGONS_TEST'

def get_associated_calibrations(filename, nbias=5):
"""
Queries Gemini Observatory Archive for associated calibrations to reduce the
data that will be used for testing.
Queries Gemini Observatory Archive for associated calibrations to reduce
the data that will be used for testing.
Parameters
----------
filename : str
Input file name
"""
pd = pytest.importorskip("pandas", minversion='1.0.0')
url = "https://archive.gemini.edu/calmgr/{}".format(filename)

url = f"https://archive.gemini.edu/calmgr/{filename}"
tree = et.parse(urllib.request.urlopen(url))
root = tree.getroot()
prefix = root.tag[:root.tag.rfind('}') + 1]

def iter_nodes(node):
rows = []
for node in tree.iter(prefix + 'calibration'):
cal_type = node.find(prefix + 'caltype').text
cal_filename = node.find(prefix + 'filename').text
return cal_filename, cal_type

cals = pd.DataFrame(
[iter_nodes(node) for node in tree.iter(prefix + 'calibration')],
columns=['filename', 'caltype'])

cals = cals.sort_values(by='filename')
cals = cals[~cals.caltype.str.contains('processed_')]
cals = cals[~cals.caltype.str.contains('specphot')]
cals = cals.drop(cals[cals.caltype.str.contains('bias')][nbias:].index)

return cals


def get_active_git_branch():
"""
Returns the name of the active GIT branch to be used in Continuous
Integration tasks and organize input/reference files.
Note: This works currently only if the remote name is "origin", though it
would be easy to adapt for other cases if needed.
Returns
-------
str or None : Name of the input active git branch. It returns None if
the branch name could not be retrieved.
"""
branch_re = r'\(HEAD.*, \w+\/(\w*)(?:,\s\w+)?\)'
git_cmd = ['git', 'log', '-n', '1', '--pretty=%d', 'HEAD']
try:
out = subprocess.check_output(git_cmd).decode('utf8')
branch_name = re.search(branch_re, out).groups()[0]
except Exception:
print("\nCould not retrieve active git branch. Make sure that the\n"
"following path is a valid Git repository: {}\n"
.format(os.getcwd()))
else:
print(f"\nRetrieved active branch name: {branch_name:s}")
return branch_name


@pytest.fixture(scope='module')
def path_to_inputs(request, env_var='DRAGONS_TEST'):
"""
PyTest fixture that returns the path to where the input files for a given
test module live.
Parameters
----------
request : fixture
PyTest's built-in fixture with information about the test itself.
env_var : str
Environment variable that contains the root path to the input data.
Returns
-------
str:
Path to the input files.
"""
path_to_test_data = os.getenv(env_var)

if path_to_test_data is None:
pytest.skip('Environment variable not set: $DRAGONS_TEST')

path_to_test_data = os.path.expanduser(path_to_test_data).strip()

module_path = request.module.__name__.split('.') + ["inputs"]
module_path = [item for item in module_path if item not in "tests"]
path = os.path.join(path_to_test_data, *module_path)

if not os.path.exists(path):
raise FileNotFoundError(
" Could not find path to input data:\n {:s}".format(path))

if not os.access(path, os.R_OK):
pytest.fail('\n Path to input test data exists but is not accessible: '
'\n {:s}'.format(path))

branch_name = get_active_git_branch()

if branch_name:
branch_name = branch_name.replace("/", "_")
path_with_branch = path.replace("/inputs", f"/inputs_{branch_name}")
path = path_with_branch if os.path.exists(path_with_branch) else path

print(f"Using the following path to the inputs:\n {path}\n")
return path


@pytest.fixture(scope='module')
def path_to_refs(request, env_var='DRAGONS_TEST'):
"""
PyTest fixture that returns the path to where the reference files for a
given test module live.
Parameters
----------
request : fixture
PyTest's built-in fixture with information about the test itself.
env_var : str
Environment variable that contains the root path to the input data.
Returns
-------
str:
Path to the reference files.
"""
path_to_test_data = os.getenv(env_var)

if path_to_test_data is None:
pytest.skip('Environment variable not set: $DRAGONS_TEST')

path_to_test_data = os.path.expanduser(path_to_test_data).strip()

module_path = request.module.__name__.split('.') + ["refs"]
module_path = [item for item in module_path if item not in "tests"]
path = os.path.join(path_to_test_data, *module_path)

if not os.path.exists(path):
pytest.fail('\n Path to reference test data does not exist: '
'\n {:s}'.format(path))

if not os.access(path, os.R_OK):
pytest.fail('\n Path to reference test data exists but is not accessible: '
'\n {:s}'.format(path))

branch_name = get_active_git_branch()

if branch_name:
branch_name = branch_name.replace("/", "_")
path_with_branch = path.replace("/refs", f"/refs_{branch_name}")
path = path_with_branch if os.path.exists(path_with_branch) else path

print(f"Using the following path to the refs:\n {path}\n")
return path


@pytest.fixture(scope='module')
def path_to_outputs(request, base_temp):
"""
PyTest fixture that creates a temporary folder to save tests outputs. You can
set the base directory by passing the ``--basetemp=mydir/`` argument to the
PyTest call (See [Pytest - Temporary Directories and Files][1]).
[1]: https://docs.pytest.org/en/stable/tmpdir.html#temporary-directories-and-files
Returns
-------
str
Path to the output data.
Raises
------
IOError
If output path does not exits.
"""
module_path = request.module.__name__.split('.')
module_path = [item for item in module_path if item not in "tests"]
path = os.path.join(base_temp, *module_path)
os.makedirs(path, exist_ok=True)
if not ('processed_' in cal_filename or 'specphot' in cal_filename):
rows.append((cal_filename, cal_type))

return path
tbl = Table(rows=rows, names=['filename', 'caltype'])
tbl.sort('filename')
tbl.remove_rows(np.where(tbl['caltype'] == 'bias')[0][nbias:])
return tbl

0 comments on commit 29a9fce

Please sign in to comment.