Skip to content

Commit 1bd2167

Browse files
authored
Merge pull request #3 from xylar/add-ci-and-unit-tests
Add unit tests and continuous integration
2 parents d8699d1 + b3f7f32 commit 1bd2167

8 files changed

Lines changed: 352 additions & 83 deletions

File tree

.github/workflows/pytest.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Pytest
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
tests:
9+
name: Run Pytest
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Check out repository
14+
uses: actions/checkout@v4
15+
16+
- name: Set up micromamba environment
17+
uses: mamba-org/setup-micromamba@v2
18+
with:
19+
environment-file: isschecker_env.yml
20+
environment-name: isschecker
21+
cache-environment: true
22+
cache-downloads: true
23+
init-shell: >-
24+
bash
25+
26+
- name: Run test suite
27+
shell: micromamba-shell {0}
28+
run: pytest -v tests/test_compliance_checker.py

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__
2+
.pytest_cache

README.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,33 @@ python compliance_checker.py --source-path ./Models/GrIS/ISMIP7/SYNTH1/CORE --va
4747

4848
## Generating synthetic test files
4949

50-
`test/generate_test_files.py` creates ISMIP7-style NetCDF test files with synthetic data. See [test/README.md](test/README.md) for full options and examples.
50+
`generate/generate_test_files.py` creates ISMIP7-style NetCDF test files with synthetic data. See [generate/README.md](generate/README.md) for full options and examples.
5151

5252
```bash
5353
conda activate isschecker
5454

5555
# Generate 286-year GrIS ctrl xyt variables
56-
python test/generate_test_files.py --grid GrIS_16000m --scenario ctrl --xyt --nyears 286 --start-year 2015
56+
python generate/generate_test_files.py --grid GrIS_16000m --scenario ctrl --xyt --nyears 286 --start-year 2015
5757

5858
# Generate 286-year AIS ctrl scalar variables
59-
python test/generate_test_files.py --grid AIS_16000m --scenario ctrl --scalars --nyears 286 --start-year 2015
59+
python generate/generate_test_files.py --grid AIS_16000m --scenario ctrl --scalars --nyears 286 --start-year 2015
6060

6161
# List available grids
62-
python test/generate_test_files.py --list-grids
62+
python generate/generate_test_files.py --list-grids
6363
```
64+
65+
---
66+
67+
## Running Tests
68+
69+
The regression suite uses `pytest` and creates temporary synthetic datasets, then mutates them to verify expected checker failures for naming, missing variables, time-axis problems, and missing attributes.
70+
71+
```bash
72+
pytest -v tests/test_compliance_checker.py
73+
```
74+
75+
If you want to retain the files generated during testing you can use:
76+
```
77+
pytest -v tests/test_compliance_checker.py --basetemp=/tmp/pytest_tmp
78+
```
79+
The files will then be left in `/tmp/pytest_tmp`. Otherwise, they are cleaned up once tests pass.

compliance_checker.py

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,28 @@ def main() -> None:
134134
args = _parse_args()
135135
source_path = args.source_path
136136
variable_list = args.variable_list
137-
workdir = os.getcwd()
138137

139-
commit_num = _get_commit_number()
138+
run_checker(
139+
source_path=source_path,
140+
variable_list=variable_list,
141+
workdir=os.getcwd(),
142+
)
143+
144+
145+
def run_checker(
146+
source_path: str,
147+
variable_list: str = DEFAULT_VARIABLE_LIST,
148+
workdir: str | None = None,
149+
commit_num: str | None = None,
150+
):
151+
workdir = os.path.abspath(workdir or os.getcwd())
152+
commit_num = _get_commit_number() if commit_num is None else commit_num
140153
experiments_ismip7 = _load_experiments_csv(
141154
os.path.join(workdir, EXPERIMENTS_ISMIP7_CSV_FILENAME)
142155
)
143156
ismip_meta, ismip_var, mandatory_variables = _load_criteria(workdir, variable_list)
144157

145-
_run_compliance_checker(
158+
summary = _run_compliance_checker(
146159
source_path=source_path,
147160
commit_num=commit_num,
148161
ismip_meta=ismip_meta,
@@ -152,6 +165,16 @@ def main() -> None:
152165
criteria_file=VARIABLE_REQUEST_XLSX,
153166
)
154167

168+
log_path = os.path.join(source_path, "compliance_checker_log.txt")
169+
log_text = ""
170+
if os.path.exists(log_path):
171+
with open(log_path, "r") as log_file:
172+
log_text = log_file.read()
173+
174+
summary["log_path"] = log_path
175+
summary["log_text"] = log_text
176+
return summary
177+
155178

156179
def _get_commit_number() -> str:
157180
try:
@@ -250,10 +273,10 @@ def _run_compliance_checker(
250273
mandatory_variables,
251274
experiments,
252275
criteria_file,
253-
) -> None:
276+
):
254277
if not os.path.isdir(source_path):
255278
print(f"ERROR: Directory not found: '{source_path}'. Please check your --source-path argument.")
256-
return
279+
return _empty_summary()
257280

258281
try:
259282
with open(os.path.join(source_path, "compliance_checker_log.txt"), "w") as f:
@@ -267,7 +290,7 @@ def _run_compliance_checker(
267290
msg = f"No .nc files found in directory '{source_path}'. Please check your --source-path argument."
268291
print(f"ERROR: {msg}")
269292
f.write(f"ERROR: {msg}\n")
270-
return
293+
return _empty_summary()
271294

272295
summary = _process_experiments(
273296
log_file=f,
@@ -292,12 +315,29 @@ def _run_compliance_checker(
292315
total_attr_errors=summary["total_attr_errors"],
293316
report_naming_issues=summary["report_naming_issues"],
294317
)
318+
return summary
295319

296320
except TypeError as err:
297321
print(
298322
"Something went wrong with your dataset. Please, check your file(s) carefully. Error:",
299323
err,
300324
)
325+
return _empty_summary()
326+
327+
328+
def _empty_summary() -> dict:
329+
return {
330+
"exp_counter": 0,
331+
"file_counter": 0,
332+
"total_errors": 0,
333+
"total_naming_errors": 0,
334+
"total_num_errors": 0,
335+
"total_spatial_errors": 0,
336+
"total_time_errors": 0,
337+
"total_attr_errors": 0,
338+
"total_file_errors": 0,
339+
"report_naming_issues": [],
340+
}
301341

302342

303343
def _group_files_by_experiment(source_path: str) -> dict:

test/README.md renamed to generate/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# ISMIP7 NetCDF generator
22

3-
`test/generate_test_files.py` creates ISMIP7-style NetCDF test files with synthetic data, one file per variable, following the naming convention and grid definitions used by the compliance checker.
3+
`generate/generate_test_files.py` creates ISMIP7-style NetCDF test files with synthetic data, one file per variable, following the naming convention and grid definitions used by the compliance checker.
44

55
Files are written to `Models/{GrIS|AIS}/ISMIP7/SYNTH1/CORE/`.
66

77
## Usage
88

99
```bash
1010
conda activate isschecker
11-
python test/generate_test_files.py [OPTIONS]
11+
python generate/generate_test_files.py [OPTIONS]
1212
```
1313

1414
## Key options
@@ -32,22 +32,22 @@ python test/generate_test_files.py [OPTIONS]
3232

3333
```bash
3434
# List available grids
35-
python test/generate_test_files.py --list-grids
35+
python generate/generate_test_files.py --list-grids
3636

3737
# Generate 286-year GrIS ctrl files (x,y,t variables)
38-
python test/generate_test_files.py --grid GrIS_16000m --scenario ctrl \
38+
python generate/generate_test_files.py --grid GrIS_16000m --scenario ctrl \
3939
--xyt --nyears 286 --start-year 2015
4040

4141
# Generate 286-year AIS ssp370 files
42-
python test/generate_test_files.py --grid AIS_08000m --scenario ssp370 \
42+
python generate/generate_test_files.py --grid AIS_08000m --scenario ssp370 \
4343
--xyt --nyears 286 --start-year 2015
4444

4545
# Generate scalar-only variables
46-
python test/generate_test_files.py --grid GrIS_16000m --scenario ctrl \
46+
python generate/generate_test_files.py --grid GrIS_16000m --scenario ctrl \
4747
--scalars --nyears 286 --start-year 2015
4848

4949
# Generate both 3D and scalar variables, including non-mandatory ones
50-
python test/generate_test_files.py --grid GrIS_16000m --scenario ctrl \
50+
python generate/generate_test_files.py --grid GrIS_16000m --scenario ctrl \
5151
--xyt --scalars --include-non-mandatory --nyears 286 --start-year 2015
5252
```
5353

0 commit comments

Comments
 (0)