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

Bugfix #2244 develop fix diff tests #2254

Merged
merged 42 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
f4e6e78
set shallow=False for file comparision because shallow compare can in…
georgemccabe Jul 12, 2023
670cabf
pop header line from file b that was incorrectly removed, add check t…
georgemccabe Jul 12, 2023
8cbf350
remove header from zip so additional columns in stat files past the l…
georgemccabe Jul 12, 2023
8b2fc0e
error if different number of columns in stat files, include column nu…
georgemccabe Jul 12, 2023
998fd47
ci-run-all-diff
georgemccabe Jul 12, 2023
f1898b3
change log messages to discern which test determined no diffs, turn o…
georgemccabe Jul 13, 2023
8207e4b
add blank lines for pep8 standards
georgemccabe Jul 13, 2023
6de3620
refactor to move logic into function, return non-zero if diffs occur …
georgemccabe Jul 13, 2023
e6c8f5f
skip diff if file path contains a keyword, skip PBL use case
georgemccabe Jul 13, 2023
6890dc3
use rounding to compare stat lines instead of exact equal
georgemccabe Jul 13, 2023
a1caa8a
added function to return boolean if directories are equal
georgemccabe Jul 13, 2023
15711ff
per #2244, add unit tests for diff utility
georgemccabe Jul 13, 2023
83b2494
per #2245, use unique run ID to name logger instance to ensure that c…
georgemccabe Jul 13, 2023
da6e5b1
per #2249, add a step to Dockerfiles to remove the conda environment …
georgemccabe Jul 14, 2023
577147d
create test.v5.1 metplus-env docker image to use for both pytests and…
georgemccabe Jul 14, 2023
723a574
use new test.v5.1 docker image for pytests
georgemccabe Jul 14, 2023
5a9548a
updated comparison that includes rounding to properly handle strings …
georgemccabe Jul 14, 2023
159493b
added unit tests for comparing stat files
georgemccabe Jul 14, 2023
d5ea70c
per #2245, add METplusConfig class function that is called when objec…
georgemccabe Jul 17, 2023
5a27a2e
Merge branch 'develop' into bugfix_2244_develop_fix_diff_tests
georgemccabe Jul 17, 2023
47795f4
add doc info
georgemccabe Jul 17, 2023
a9622df
call all pytests in single call instead of splitting them up by marker
georgemccabe Jul 17, 2023
0e8e7d4
purposely break 1 test to ensure automated test captures failure
georgemccabe Jul 17, 2023
c0ad3e4
remove commented code after confirming tests run from single command
georgemccabe Jul 17, 2023
8b599de
fix test that was broken on purpose and add check that number of comm…
georgemccabe Jul 17, 2023
f30277b
turn off all use cases for push events
georgemccabe Jul 17, 2023
93d26e6
skip test for now to remove dependency on file from MET install dir
georgemccabe Jul 17, 2023
457987c
remove test that is not needed -- tests do not need to run MET tools
georgemccabe Jul 17, 2023
8bb277e
update logic to allow wrapper to initialize properly if R scripts do …
georgemccabe Jul 17, 2023
e1dd5c6
Removed logic to require minimum_pytest.<host>.sh script to run pytes…
georgemccabe Jul 18, 2023
702352a
error and exit if output base is set to a directory where the user do…
georgemccabe Jul 18, 2023
01bfe21
include another valid NetCDF extension
georgemccabe Jul 18, 2023
3e6616a
fix logic to check if a pixel in an image has no differences
georgemccabe Jul 18, 2023
596a8ef
test if png diff logic will work, ci-run-all-diff
georgemccabe Jul 18, 2023
26562d5
strip off whitespace before diffing column values
georgemccabe Jul 18, 2023
3b906a3
add more tests for csv and stat files. move multiple tests into a sin…
georgemccabe Jul 18, 2023
59de5c9
remove file containing pytest groups because they are no longer requi…
georgemccabe Jul 18, 2023
0982a55
add diff marker to be able to run all unit tests except the diff util…
georgemccabe Jul 18, 2023
d3033c6
per #2244, updated documentation about running unit tests. removed se…
georgemccabe Jul 18, 2023
c147404
added info about using metplus_config fixture in unit tests
georgemccabe Jul 18, 2023
10ae204
set DO_NOT_RUN_EXE for all tests to prevent the actual commands from …
georgemccabe Jul 18, 2023
e2db1cf
clean up documentation about unit tests
georgemccabe Jul 18, 2023
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
16 changes: 4 additions & 12 deletions .github/actions/run_tests/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ WS_PATH=$RUNNER_WORKSPACE/$REPO_NAME
# set CI jobs directory variable to easily move it
CI_JOBS_DIR=.github/jobs

PYTESTS_GROUPS_FILEPATH=.github/parm/pytest_groups.txt

source ${GITHUB_WORKSPACE}/${CI_JOBS_DIR}/bash_functions.sh

# get branch name for push or pull request events
Expand All @@ -34,7 +32,7 @@ fi

# running unit tests (pytests)
if [[ "$INPUT_CATEGORIES" == pytests* ]]; then
export METPLUS_ENV_TAG="pytest.v5.1"
export METPLUS_ENV_TAG="test.v5.1"
export METPLUS_IMG_TAG=${branch_name}
echo METPLUS_ENV_TAG=${METPLUS_ENV_TAG}
echo METPLUS_IMG_TAG=${METPLUS_IMG_TAG}
Expand All @@ -56,15 +54,9 @@ if [[ "$INPUT_CATEGORIES" == pytests* ]]; then
.

echo Running Pytests
command="export METPLUS_PYTEST_HOST=docker; cd internal/tests/pytests;"
command+="status=0;"
for x in `cat $PYTESTS_GROUPS_FILEPATH`; do
marker="${x//_or_/ or }"
marker="${marker//not_/not }"
command+="/usr/local/conda/envs/${METPLUS_ENV_TAG}/bin/pytest -vv --cov=../../../metplus --cov-append -m \"$marker\""
command+=";if [ \$? != 0 ]; then status=1; fi;"
done
command+="if [ \$status != 0 ]; then echo ERROR: Some pytests failed. Search for FAILED to review; false; fi"
command="export METPLUS_TEST_OUTPUT_BASE=/data/output;"
command+="/usr/local/conda/envs/${METPLUS_ENV_TAG}/bin/pytest internal/tests/pytests -vv --cov=metplus --cov-append --cov-report=term-missing;"
command+="if [ \$? != 0 ]; then echo ERROR: Some pytests failed. Search for FAILED to review; false; fi"
time_command docker run -v $WS_PATH:$GITHUB_WORKSPACE --workdir $GITHUB_WORKSPACE $RUN_TAG bash -c "$command"
exit $?
fi
Expand Down
4 changes: 4 additions & 0 deletions .github/jobs/run_diff_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
OUTPUT_DIR = '/data/output'
DIFF_DIR = '/data/diff'


def copy_diff_output(diff_files):
"""! Loop through difference output and copy files
to directory so it can be made available for comparison.
Expand All @@ -45,6 +46,7 @@ def copy_diff_output(diff_files):
copy_to_diff_dir(diff_file,
'diff')


def copy_to_diff_dir(file_path, data_type):
"""! Generate output path based on input file path,
adding text based on data_type to the filename, then
Expand Down Expand Up @@ -85,6 +87,7 @@ def copy_to_diff_dir(file_path, data_type):

return True


def main():
print('******************************')
print("Comparing output to truth data")
Expand All @@ -97,5 +100,6 @@ def main():
if diff_files:
copy_diff_output(diff_files)


if __name__ == '__main__':
main()
8 changes: 0 additions & 8 deletions .github/parm/pytest_groups.txt

This file was deleted.

130 changes: 91 additions & 39 deletions docs/Contributors_Guide/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,67 @@ directory.
Unit Tests
----------

Unit tests are run with pytest. They are found in the *pytests* directory.
Unit tests are run with pytest.
They are found in the *internal/tests/pytests* directory under the *wrappers*
and *util* directories.
Each tool has its own subdirectory containing its test files.

Unit tests can be run by running the 'pytest' command from the
internal/tests/pytests directory of the repository.
The 'pytest' Python package must be available.
Pytest Requirements
^^^^^^^^^^^^^^^^^^^

The following Python packages are required to run the tests.

* **pytest**: Runs the tests
* **python-dateutil**: Required to run METplus wrappers
* **netCDF4**: Required for some METplus wrapper functionality
* **pytest-cov** (optional): Only if generating code coverage stats
* **pillow** (optional): Only used if running diff utility tests
* **pdf2image** (optional): Only used if running diff utility tests

Running
^^^^^^^

To run the unit tests, set the environment variable
**METPLUS_TEST_OUTPUT_BASE** to a path where the user running has write
permissions, nativate to the METplus directory, then call pytest::

export METPLUS_TEST_OUTPUT_BASE=/d1/personal/${USER}/pytest
cd METplus
pytest internal/tests/pytests

A report will be output showing which pytest categories failed.
When running on a new computer, a **minimum_pytest.<HOST>.sh**
file must be created to be able to run the script. This file contains
information about the local environment so that the tests can run.
To view verbose test output, add the **-vv** argument::

pytest internal/tests/pytests -vv

Code Coverage
^^^^^^^^^^^^^

If the *pytest-cov* package is installed, the code coverage report can
be generated from the tests by running::

pytest internal/tests/pytests --cov=metplus --cov-report=term-missing

In addition to the pass/fail report, the code coverage information will be
displayed including line numbers that are not covered by any test.

All unit tests must include one of the custom markers listed in the
Subsetting Tests by Directory
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

A subset of the unit tests can be run by adjusting the path.
Be sure to include the *--cov-append* argument so the results of the run
are appended to the full code coverage results.
To run only the GridStat unit tests::

pytest internal/tests/pytests/wrappers/grid_stat --cov=metplus --cov-report=term-missing --cov-append


Subsetting Tests by Marker
^^^^^^^^^^^^^^^^^^^^^^^^^^
Unit tests can include one of the custom markers listed in the
internal/tests/pytests/pytest.ini file. Some examples include:

* diff
* run_metplus
* util
* wrapper_a
Expand All @@ -44,47 +91,52 @@ New pytest markers should be added to the pytest.ini file with a brief
description. If they are not added to the markers list, then a warning will
be output when running the tests.

There are many unit tests for METplus and false failures can occur if all of
the are attempted to run at once.
To run only tests with a given marker, run::

pytest -m <MARKER-NAME>
pytest internal/tests/pytests -m <MARKER-NAME>

To run all tests that do not have a given marker, run::

pytest -m "not <MARKER-NAME>"
pytest internal/tests/pytests -m "not <MARKER-NAME>"

For example, **if you are running on a system that does not have the additional
dependencies required to run the diff utility tests**, you can run all of the
tests except those by running::

pytest internal/tests/pytests -m "not diff"

Multiple marker groups can be run by using the *or* keyword::

pytest internal/tests/pytests -m "<MARKER-NAME1> or <MARKER-NAME2>"

Writing Unit Tests
^^^^^^^^^^^^^^^^^^

metplus_config fixture
""""""""""""""""""""""

Multiple marker groups can be run by using the 'or' keyword::
Many unit tests utilize a pytest fixture named **metplus_config**.
This is defined in the **conftest.py** file in internal/tests/pytests.
This is used to create a METplusConfig object that contains the minimum
configurations needed to run METplus, like **OUTPUT_BASE**.
Using this fixture in a pytest will initialize the METplusConfig object to use
in the tests.

pytest -m "<MARKER-NAME1> or <MARKER-NAME2>"
This also creates a unique output directory for each test where
logs and output files are written. This directory is created under
**$METPLUS_TEST_OUTPUT_BASE**/test_output and is named with the run ID.
If the test passes, then the output directory is automatically removed.
If the test fails, the output directory will not be removed so the content
can be reviewed to debug the issue.

To use it, add **metplus_config** as an argument to the test function::

Use Case Tests
--------------
def test_something(metplus_config)

Use case tests are run via a Python script called **test_use_cases.py**,
found in the *use_cases* directory.
Eventually the running of these tests will be automated using an external
tool, such as GitHub Actions or Travis CI.
The script contains a list of use cases that are found in the repository.
For each computer that will run the use cases, a
**metplus_test_env.<HOST>.sh** file must exist to set local configurations.
All of the use cases can be run by executing the script
**run_test_use_cases.sh**. The use case test script will output the results
into a directory such as */d1/<USER>/test-use-case-b*, defined in the
environment file.
If */d1/<USER>/test-use-case-b* already exists, its content will be copied
over to */d1/<USER>/test-use-case-a*. If data is found in
the */d1/<USER>/test-use-case-b* directory already exists, its content
will be copied
over to the */d1/<USER>/test-use-case-a* directory, the script will prompt
the user to remove those files.
Once the tests have finished running, the output found in the two
directories can be compared to see what has changed. Suggested commands
to run to compare the output will be shown on the screen after completion
of the script.
then set a variable called **config** using the fixture name::

To see which files and directories are only found in one run::
config = metplus_config

diff -r /d1/mccabe/test-use-case-a /d1/mccabe/test-use-case-b | grep Only
Additional configuration variables can be set by using the set method::

config.set('config', key, value)
5 changes: 5 additions & 0 deletions internal/scripts/docker_env/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ ARG METPLUS_ENV_VERSION
ARG ENV_NAME
RUN conda list --name ${ENV_NAME}.${METPLUS_ENV_VERSION} > \
/usr/local/conda/envs/${ENV_NAME}.${METPLUS_ENV_VERSION}/environments.yml

# remove base environment to free up space
ARG METPLUS_ENV_VERSION
ARG BASE_ENV=metplus_base
RUN conda env remove -y --name ${BASE_ENV}.${METPLUS_ENV_VERSION}
5 changes: 5 additions & 0 deletions internal/scripts/docker_env/Dockerfile.cartopy
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ RUN apt update && apt -y upgrade \
&& rm -f cartopy_feature_download.py \
&& curl https://raw.githubusercontent.com/SciTools/cartopy/master/tools/cartopy_feature_download.py > cartopy_feature_download.py \
&& /usr/local/conda/envs/${ENV_NAME}.${METPLUS_ENV_VERSION}/bin/python3 cartopy_feature_download.py cultural physical

# remove base environment to free up space
ARG METPLUS_ENV_VERSION
ARG BASE_ENV=metplus_base
RUN conda env remove -y --name ${BASE_ENV}.${METPLUS_ENV_VERSION}
10 changes: 5 additions & 5 deletions internal/scripts/docker_env/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,20 +426,20 @@ export METPLUS_ENV_VERSION=v5.1



## pytest.v5.1 (from metplus_base.v5.1)
## test.v5.1 (from metplus_base.v5.1)

This environment is used in automation to run the pytests. It requires all of the
This environment is used in automation to run the pytests and diff tests. It requires all of the
packages needed to run all of the METplus wrappers, the pytest package and the pytest
code coverage package.

### Docker

```
export METPLUS_ENV_VERSION=v5.1
docker build -t dtcenter/metplus-envs:pytest.${METPLUS_ENV_VERSION} \
docker build -t dtcenter/metplus-envs:test.${METPLUS_ENV_VERSION} \
--build-arg METPLUS_ENV_VERSION \
--build-arg ENV_NAME=pytest .
docker push dtcenter/metplus-envs:pytest.${METPLUS_ENV_VERSION}
--build-arg ENV_NAME=test .
docker push dtcenter/metplus-envs:test.${METPLUS_ENV_VERSION}
```


Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
#! /bin/sh

################################################################################
# Environment: pytest.v5.1
# Last Updated: 2023-01-31 (mccabe@ucar.edu)
# Environment: test.v5.1
# Last Updated: 2023-07-14 (mccabe@ucar.edu)
# Notes: Adds pytest and pytest coverage packages to run unit tests
# Added pandas because plot_util test needs it
# Added netcdf4 because SeriesAnalysis test needs it
# Added pillow and pdf2image for diff tests
# Python Packages:
# TODO: update version numbers
# pytest==?
# pytest-cov==?
# pandas==?
# netcdf4==?
# pillow==?
# pdf2image==?
#
# Other Content: None
################################################################################
Expand All @@ -19,7 +23,7 @@
METPLUS_VERSION=$1

# Conda environment to create
ENV_NAME=pytest.${METPLUS_VERSION}
ENV_NAME=test.${METPLUS_VERSION}

# Conda environment to use as base for new environment
BASE_ENV=metplus_base.${METPLUS_VERSION}
Expand All @@ -29,3 +33,9 @@ conda install -y --name ${ENV_NAME} -c conda-forge pytest
conda install -y --name ${ENV_NAME} -c conda-forge pytest-cov
conda install -y --name ${ENV_NAME} -c conda-forge pandas
conda install -y --name ${ENV_NAME} -c conda-forge netcdf4
conda install -y --name ${ENV_NAME} -c conda-forge pillow

apt-get update
apt-get install -y poppler-utils

conda install -y --name ${ENV_NAME} -c conda-forge pdf2image
51 changes: 13 additions & 38 deletions internal/tests/pytests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,48 +13,23 @@

from metplus.util import config_metplus

# get host from either METPLUS_PYTEST_HOST or from actual host name
# Look for minimum_pytest.<pytest_host>.sh script to source
# error and exit if not found
pytest_host = os.environ.get('METPLUS_PYTEST_HOST')
if pytest_host is None:
import socket
pytest_host = socket.gethostname()
print("No hostname provided with METPLUS_PYTEST_HOST, "
f"using {pytest_host}")
else:
print(f"METPLUS_PYTEST_HOST = {pytest_host}")

minimum_pytest_file = os.path.join(os.path.dirname(__file__),
f'minimum_pytest.{pytest_host}.sh')
if not os.path.exists(minimum_pytest_file):
print(f"ERROR: minimum_pytest.{pytest_host}.sh file must exist in "
"pytests directory. Set METPLUS_PYTEST_HOST correctly or "
"create file to run pytests on this host.")
sys.exit(4)

# source minimum_pytest.<pytest_host>.sh script
current_user = getpass.getuser()
command = shlex.split(f"env -i bash -c 'export USER={current_user} && "
f"source {minimum_pytest_file} && env'")
proc = subprocess.Popen(command, stdout=subprocess.PIPE)

for line in proc.stdout:
line = line.decode(encoding='utf-8', errors='strict').strip()
key, value = line.split('=')
os.environ[key] = value

proc.communicate()

output_base = os.environ['METPLUS_TEST_OUTPUT_BASE']
output_base = os.environ.get('METPLUS_TEST_OUTPUT_BASE')
if not output_base:
print('ERROR: METPLUS_TEST_OUTPUT_BASE must be set to a path to write')
sys.exit(1)

test_output_dir = os.path.join(output_base, 'test_output')
if os.path.exists(test_output_dir):
print(f'Removing test output dir: {test_output_dir}')
shutil.rmtree(test_output_dir)
try:
test_output_dir = os.path.join(output_base, 'test_output')
if os.path.exists(test_output_dir):
print(f'Removing test output dir: {test_output_dir}')
shutil.rmtree(test_output_dir)

if not os.path.exists(test_output_dir):
print(f'Creating test output dir: {test_output_dir}')
os.makedirs(test_output_dir)
except PermissionError:
print(f'ERROR: Cannot write to $METPLUS_TEST_OUTPUT_BASE: {output_base}')
sys.exit(2)


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
Expand Down
7 changes: 4 additions & 3 deletions internal/tests/pytests/minimum_pytest.conf
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
[config]
INPUT_BASE = {ENV[METPLUS_TEST_INPUT_BASE]}
INPUT_BASE = {ENV[METPLUS_TEST_OUTPUT_BASE]}/input
OUTPUT_BASE = {ENV[METPLUS_TEST_OUTPUT_BASE]}/test_output/{RUN_ID}
MET_INSTALL_DIR = {ENV[METPLUS_TEST_MET_INSTALL_DIR]}
TMP_DIR = {ENV[METPLUS_TEST_TMP_DIR]}
MET_INSTALL_DIR = {ENV[METPLUS_TEST_OUTPUT_BASE]}

DO_NOT_RUN_EXE = True

LOG_LEVEL = DEBUG
LOG_LEVEL_TERMINAL = WARNING
Expand Down
Loading