Skip to content

Commit e05b097

Browse files
337 Add config tests (#400)
Fixes #337 . ### Description This PR adds unit tests scripts, as well as test files on brats segmentation and spleen ct segmentation bundles. Tests are for both single GPU and multiple GPUs. In addition, the PR also enhances the current Blossom CI pipelines. For PRs that do not need to use GPU or multiple GPUs, the pipeline will not wait for resources first. ### Status **Ready** ### Please ensure all the checkboxes: <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Codeformat tests passed locally by running `./runtests.sh --codeformat`. - [ ] In-line docstrings updated. - [ ] Update `version` and `changelog` in `metadata.json` if changing an existing bundle. - [ ] Please ensure the naming rules in config files meet our requirements (please refer to: `CONTRIBUTING.md`). - [ ] Ensure versions of packages such as `monai`, `pytorch` and `numpy` are correct in `metadata.json`. - [ ] Descriptions should be consistent with the content, such as `eval_metrics` of the provided weights and TorchScript modules. - [ ] Files larger than 25MB are excluded and replaced by providing download links in `large_file.yml`. - [ ] Avoid using path that contains personal information within config files (such as use `/home/your_name/` for `"bundle_root"`). --------- Signed-off-by: Yiheng Wang <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent cea52ae commit e05b097

17 files changed

+826
-18
lines changed

CONTRIBUTING.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ In train config file (if exists), please follow the following requirements in or
8080
1. If `ValidationHandler` is used, please define the key `val_interval` and use it for the argument `interval`.
8181

8282

83-
## Verifying the bundle
83+
## Verifying a bundle
8484

8585
We prepared several premerge CI tests to verify your bundle.
8686

@@ -89,7 +89,7 @@ We prepared several premerge CI tests to verify your bundle.
8989
For dependencies that support `pip install` command, please put them into `optional_packages_version` in `configs/metadata.json` ([click here for instance](https://github.com/Project-MONAI/model-zoo/blob/dev/models/brats_mri_segmentation/configs/metadata.json)), and the CI test program will extract and install all libraries directly before running tests.
9090
For dependencies that require multiple steps to install, please prepare a install script in `ci/install_scripts`, and put the bundle name and the script path into `install_dependency_dict` in `ci/bundle_custom_data.py` ([click here for instance](https://github.com/Project-MONAI/model-zoo/tree/dev/ci/install_scripts/)).
9191

92-
### Necessary verifications
92+
### Necessary tests
9393

9494
1. Check if necessary files are existing in your bundle.
9595
1. Check if keys naming are consistent with our requirements.
@@ -100,7 +100,7 @@ For dependencies that require multiple steps to install, please prepare a instal
100100
python -m monai.bundle verify_metadata --meta_file configs/metadata.json --filepath eval/schema.json
101101
```
102102

103-
### Optional verifications
103+
### Optional tests
104104

105105
#### Verify data shape and data type
106106
Check if the input and output data shape and data type of network defined in the metadata are correct. You can also run the following command locally to verify your bundle before submitting a pull request.
@@ -156,11 +156,15 @@ python -m monai.bundle run --config_file "['configs/inference.json', 'configs/in
156156

157157
If your bundle does not support TensorRT compilation, please mention it in `docs/README.md`.
158158

159+
#### Customized unit tests
160+
It is recommended to prepare unit tests on your bundle. The main purpose is to ensure each config file is runnable.
161+
Please create a file called `test_<bundle_name>.py` in `ci/unit_tests/` (like this [example](./ci/unit_tests/test_spleen_ct_segmentation.py)), and you can define the testing scope within the file.
162+
If multi-gpu config files are also need to be tested, please create a separate file called `test_<bundle_name>_dist.py` in the same directory (like this [example](./ci/unit_tests/test_spleen_ct_segmentation_dist.py)).
159163

160-
## Checking the coding style
164+
### Code format tests
161165

162-
After verifying your bundle, if there are any `.py` files, coding style is checked and enforced by flake8, black, isort, pytype and mypy.
163-
Before submitting a pull request, we recommend that all checks should pass, by running the following command locally:
166+
If there are any `.py` files in your bundle, coding style is checked and enforced by `flake8`, `black`, `isort` and `pytype`.
167+
Before submitting a pull request, we recommend that all checks should pass by running the following command locally:
164168

165169
```bash
166170
# optionally update the dependencies and dev tools
@@ -191,6 +195,7 @@ Ideally, the new branch should be based on the latest `dev` branch.
191195

192196
[monai model zoo issue list]: https://github.com/Project-MONAI/model-zoo/issues
193197

198+
194199
## Validate and release
195200

196201
As for a pull request, a CI program will try to download all large files if mentioned and do several validations. If the pull request is approved and merged, the full bundle (with all large files if exists) will be archived and send to [Releases](https://github.com/Project-MONAI/model-zoo/releases).

ci/get_bundle_requirements.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818

1919

2020
def get_requirements(bundle, models_path):
21+
"""
22+
This function is used to produce a requirements txt file, and print a string
23+
which shows the filename. The printed string can be used in shell scripts.
24+
"""
2125
bundle_path = os.path.join(models_path, bundle)
2226
meta_file_path = os.path.join(bundle_path, "configs/metadata.json")
2327
if os.path.exists(meta_file_path):

ci/get_changed_bundle.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
from utils import get_changed_bundle_list
1616

1717

18-
def main(changed_dirs):
18+
def get_changed_bundle(changed_dirs):
19+
"""
20+
This function is used to get all changed bundles, a string which
21+
contains all bundle names will be printed, and can be used in shell scripts.
22+
"""
1923
bundle_names = ""
2024
root_path = "models"
2125
bundle_list = get_changed_bundle_list(changed_dirs, root_path=root_path)
@@ -30,4 +34,4 @@ def main(changed_dirs):
3034
parser.add_argument("-f", "--f", type=str, help="changed files.")
3135
args = parser.parse_args()
3236
changed_dirs = args.f.splitlines()
33-
main(changed_dirs)
37+
get_changed_bundle(changed_dirs)

ci/get_required_resources.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright (c) MONAI Consortium
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
# Unless required by applicable law or agreed to in writing, software
7+
# distributed under the License is distributed on an "AS IS" BASIS,
8+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
# See the License for the specific language governing permissions and
10+
# limitations under the License.
11+
12+
13+
import argparse
14+
import os
15+
16+
17+
def get_changed_bundle_list(changed_dirs, root_path: str = "models"):
18+
"""
19+
This function is used to return all bundle names that have changed files.
20+
If a bundle is totally removed, it will be ignored (since it not exists).
21+
A similar function is implemented in "utils.py", but that file requires monai.
22+
In order to minimize the required libraries when running this file, this function
23+
is defined.
24+
25+
"""
26+
bundles = [f.name for f in os.scandir(root_path) if f.is_dir()]
27+
28+
changed_bundle_list = []
29+
for sub_dir in changed_dirs:
30+
for bundle in bundles:
31+
bundle_path = os.path.join(root_path, bundle)
32+
if os.path.commonpath([bundle_path]) == os.path.commonpath([bundle_path, sub_dir]):
33+
changed_bundle_list.append(bundle)
34+
35+
return list(set(changed_bundle_list))
36+
37+
38+
def get_required_resources(changed_dirs):
39+
"""
40+
This function is used to determine if GPU or multi-GPU resources are needed.
41+
A string which contains two flags will be printed, and can be used in shell scripts.
42+
"""
43+
gpu_flag, mgpu_flag = False, False
44+
root_path, unit_test_path = "models", "ci/unit_tests"
45+
bundle_list = get_changed_bundle_list(changed_dirs, root_path=root_path)
46+
if len(bundle_list) > 0:
47+
gpu_flag = True
48+
for bundle in bundle_list:
49+
mgpu_test_file = os.path.join(unit_test_path, f"test_{bundle}_dist.py")
50+
if os.path.exists(mgpu_test_file):
51+
mgpu_flag = True
52+
print(f"{gpu_flag} {mgpu_flag}")
53+
54+
55+
if __name__ == "__main__":
56+
parser = argparse.ArgumentParser(description="")
57+
parser.add_argument("-f", "--f", type=str, help="changed files.")
58+
args = parser.parse_args()
59+
changed_dirs = args.f.splitlines()
60+
get_required_resources(changed_dirs)

ci/get_required_resources.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
#
3+
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
18+
head_ref=$(git rev-parse HEAD)
19+
git fetch origin dev $head_ref
20+
changes=$(git diff --name-only $head_ref origin/dev -- models)
21+
if [ ! -z "$changes" ]
22+
then
23+
flags=$(python $(pwd)/ci/get_required_resources.py --f "$changes")
24+
else
25+
flags=$"False False"
26+
fi
27+
echo $flags

ci/run_premerge_gpu.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ verify_bundle() {
7373
fi
7474
# verify bundle
7575
pipenv run python $(pwd)/ci/verify_bundle.py --b "$bundle"
76+
# do unit tests
77+
pipenv run python $(pwd)/ci/unit_tests/runner.py --b "$bundle"
7678
remove_pipenv
7779
done
7880
else

ci/run_premerge_multi_gpu.sh

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/bin/bash
2+
#
3+
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
# Argument(s):
19+
# BUILD_TYPE: all/specific_test_name, tests to execute
20+
21+
set -ex
22+
BUILD_TYPE=all
23+
24+
if [[ $# -eq 1 ]]; then
25+
BUILD_TYPE=$1
26+
27+
elif [[ $# -gt 1 ]]; then
28+
echo "ERROR: too many parameters are provided"
29+
exit 1
30+
fi
31+
32+
init_pipenv() {
33+
echo "initializing pip environment: $1"
34+
pipenv install update pip wheel
35+
pipenv install --python=3.8 -r $1
36+
export PYTHONPATH=$PWD
37+
}
38+
39+
remove_pipenv() {
40+
echo "removing pip environment"
41+
pipenv --rm
42+
rm Pipfile Pipfile.lock
43+
}
44+
45+
verify_bundle() {
46+
echo 'Run verify bundle...'
47+
init_pipenv requirements-dev.txt
48+
head_ref=$(git rev-parse HEAD)
49+
git fetch origin dev $head_ref
50+
# achieve all changed files in 'models'
51+
changes=$(git diff --name-only $head_ref origin/dev -- models)
52+
if [ ! -z "$changes" ]
53+
then
54+
# get all changed bundles
55+
bundle_list=$(pipenv run python $(pwd)/ci/get_changed_bundle.py --f "$changes")
56+
if [ ! -z "$bundle_list" ]
57+
then
58+
pipenv run python $(pwd)/ci/prepare_schema.py --l "$bundle_list"
59+
for bundle in $bundle_list;
60+
do
61+
init_pipenv requirements-dev.txt
62+
# get required libraries according to the bundle's metadata file
63+
requirements=$(pipenv run python $(pwd)/ci/get_bundle_requirements.py --b "$bundle")
64+
if [ ! -z "$requirements" ]; then
65+
echo "install required libraries for bundle: $bundle"
66+
pipenv install -r "$requirements"
67+
fi
68+
# get extra install script if exists
69+
extra_script=$(pipenv run python $(pwd)/ci/get_bundle_requirements.py --b "$bundle" --get_script True)
70+
if [ ! -z "$extra_script" ]; then
71+
echo "install extra libraries with script: $extra_script"
72+
bash $extra_script
73+
fi
74+
# do multi gpu based unit tests
75+
pipenv run python $(pwd)/ci/unit_tests/runner.py --b "$bundle" --dist True
76+
remove_pipenv
77+
done
78+
else
79+
echo "this pull request does not change any bundles, skip verify."
80+
fi
81+
else
82+
echo "this pull request does not change any files in 'models', skip verify."
83+
remove_pipenv
84+
fi
85+
}
86+
87+
case $BUILD_TYPE in
88+
89+
all)
90+
echo "Run all tests..."
91+
verify_bundle
92+
;;
93+
94+
verify_bundle)
95+
verify_bundle
96+
;;
97+
98+
*)
99+
echo "ERROR: unknown parameter: $BUILD_TYPE"
100+
;;
101+
esac

0 commit comments

Comments
 (0)