Skip to content

Commit

Permalink
Merge pull request #147 from initze/feature/tests
Browse files Browse the repository at this point in the history
Add testing infrastructure
  • Loading branch information
initze committed Jul 17, 2024
2 parents e56bb40 + 92dc344 commit f79849c
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 7 deletions.
64 changes: 60 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,30 +89,30 @@ Run in dev:

```sh
$ rye run thaw-slump-segmentation hello tobi
Hello tobi
Hello, tobi!
```

or run as python module:

```sh
$ rye run python -m thaw_slump_segmentation hello tobi
Hello tobi
Hello, tobi!
```

With activated env, e.g. after installation, just remove the `rye run`:

```sh
$ source .venv/bin/activate
$ thaw-slump-segmentation hello tobi
Hello tobi
Hello, tobi!
```

or

```sh
$ source .venv/bin/activate
$ python -m thaw_slump_segmentation hello tobi
Hello tobi
Hello, tobi!
```

## Data Processing
Expand Down Expand Up @@ -231,3 +231,59 @@ visualization_tiles:
20190727_160426_104e: [5, 52, 75, 87, 113, 139, 239, 270, 277, 291, 305]

```

### Tests

We use [Pytest](https://pytest.org/) as testing library for unit tests.

Run tests (via rye, for other environments activate the environment
with `.venv/bin/activate` or similar and then simply remove the `rye run` before the command):

```sh
rye run pytest
```

Run all tests from a specific file:

```sh
rye run pytest tests/test_hello.py
```

Run a specific test from a specific file:

```sh
rye run pytest
```

#### Test Parameters

Several arguments can be passed to pytest to configure the environment for the tests, but not all test parameters are needed by all tests. Tests needing a parameter are skipped if the parameter is missing. In case the reason for skipping a test is of interest, [the argument `-rs`](https://docs.pytest.org/en/6.2.x/usage.html#detailed-summary-report) can be passed to pytest.

##### `--data_dir`

Most tests need input data which will be imported from a data directory as root. The tests expect a specific folder structure below the root. For now this is:
* a folder `raw_data_dir` where the folders `scenes` and `tiles` exist for the planet source data
* a folder `auxiliary` with the subfolder `ArcticDEM` where the virtual raster files reside, which are pointing to the slope and relative elevation data.

No files are changed within those directories. The data is copied to a temporary directory and the tests are run there.

##### `--gdal_bin`, `--gdal_path`

Paths to the folders where the gdal executables (like `gdaltransform`, `--gdal_bin`) and gdal scripts like `gdal_retile.py` (`--gdal_path`) reside.

##### `--proj_data_env`

The proj library is needed for some processes by the gdal binaries and scripts. In case it cannot find or open its data folder, the PROJ_DATA environment variable needs to be set. Passing the parameter `--proj_data_env` to pytest will set this environment variable during the tests.

### Todos testing

* [ ] Add GitHub Workflow for [Pytest](https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#testing-your-code) and [Ruff](https://github.com/chartboost/ruff-action)
* [ ] Add tests for data-related scripts (single and multiple files (for multiprocessing) each):
* [ ] Setup Raw Data
* [x] Prepare data
* [ ] Inference
* [ ] (S2 related scripts)
* [ ] Utilities
* [ ] GDAL init
* [ ] EE auth + init
* [ ] TBD
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ dependencies = [
"opencv-python>=4.9.0.80",
"swifter>=1.4.0",
"mkdocs-awesome-pages-plugin>=2.9.2",
"ruff>=0.4.8",
"ipykernel>=6.29.4",
"rich>=13.7.1",
"torchsummary>=1.5.1",
"typer>=0.12.3",
Expand All @@ -72,10 +70,13 @@ build-backend = "hatchling.build"
[tool.rye]
managed = true
dev-dependencies = [
"ruff>=0.4.2",
"ipykernel>=6.29.4",
"ipython>=8.23.0",
"mkdocs-material>=9.5.19",
"mkdocstrings[python]>=0.24.3",
"mkdocs-gen-files>=0.5.0",
"pytest>=8.2.2",
]

[tool.hatch.metadata]
Expand All @@ -94,3 +95,6 @@ line-length = 120
[tool.ruff.format]
quote-style = "single"
docstring-code-format = true

[tool.pytest.ini_options]
addopts = ["--import-mode=importlib"]
3 changes: 2 additions & 1 deletion src/thaw_slump_segmentation/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@

@cli.command()
def hello(name: str):
typer.echo(f'Hello {name}')
typer.echo(f'Hello, {name}!')
return f'Hello, {name}!'


cli.command()(train)
Expand Down
124 changes: 124 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import pytest
from pathlib import Path
import os, warnings, subprocess

def pytest_addoption(parser):
# adds a test parameter --data-dir to pass a directory where predefined testfiles reside
# the testfiles will be configured as a fixture
# tests which request this directory will be skipped if this parameter isn't passed.
parser.addoption(
"--data_dir",
action="store",
default=None,
help="Directory containing the predefined data files"
)

# GDAL config :
parser.addoption(
"--gdal_bin",
action="store",
default=None,
help='Path to gdal binaries'
)
parser.addoption(
"--gdal_path",
action="store",
default=None,
help='Path to gdal python scripts (e.g. gdal_retile.py), might be the same as --gdal_bin'
)
parser.addoption(
"--proj_data_env",
action="store",
default=None,
help='path to data of the proj library, will be set as PROJ_DATA environment variable'
)


# the fixture for the data dir:
@pytest.fixture
def data_dir(request):
p = request.config.getoption("--data_dir")

if p is None:
pytest.skip( f"parameter --data_dir needed for this test" )

data_path_root = Path(p)

if not data_path_root.exists():
pytest.skip( f" {data_path_root} does not exist " )

return data_path_root


#
# ========== GDAL related fixtures =========================================
#
# take note if we ought to test if GDAL is available:
# 0 -> GDAL not tested
# 1 -> GDAL tested and works
# -1 -> GDAL tested and does NOT work
GDAL_TEST = 0

# the proj_data fixture will be imported by the gdal_{bin|path} fixtures
@pytest.fixture()
def proj_data(request):
proj_data = request.config.getoption("--proj_data_env")
if proj_data is not None:
os.environ["PROJ_DATA"] = proj_data

@pytest.fixture
def gdal_bin(request, proj_data):
global GDAL_TEST
gdal_bin = request.config.getoption("--gdal_bin")
if gdal_bin is None:
pytest.skip( f"parameter --gdal_bin needed for this test" )

if GDAL_TEST == 0:
# not tested yet
# check if gdal via subprocess (as we do it in the scripts) works:
gdal_bin_path = Path(gdal_bin)

for bin_tool in [ 'gdal_rasterize', 'gdal_translate', 'gdalwarp', 'gdaltransform' ]:
bin_tool_path = gdal_bin_path / bin_tool

if not bin_tool_path.exists():
pytest.skip( f"--gdal_bin is required to point to the folder of the gdal binaries (e.g. gdalwarp) " )


gdaltransform = gdal_bin_path / "gdaltransform"
shell_command = f'echo "12 34" | {gdaltransform} -s_srs EPSG:4326 -t_srs EPSG:3857'
proc = subprocess.run(shell_command, shell=True, capture_output=True, text=True)

if proc.returncode > 0:
GDAL_TEST = -1
warning_message = (f"testing GDAL with {shell_command} failed: \n"
f"{proc.stderr}\n"
"You might want to pass --proj_data_env to pytest pointing to the proj database folder"
)
warnings.warn(warning_message)
else:
GDAL_TEST = 1

if GDAL_TEST == -1:
pytest.skip( f"a working gdal is required for this test" )

return gdal_bin

@pytest.fixture
def gdal_path(request, proj_data):
gdal_path = request.config.getoption("--gdal_path")
if gdal_path is None:
pytest.skip( f"parameter --gdal_path needed for this test" )


pygdal_bin_path = Path(gdal_path)

for py_tool in [ 'gdal_merge.py', 'gdal_retile.py', 'gdal_polygonize.py' ]:
pygdal_tool_path = pygdal_bin_path / py_tool

if not pygdal_tool_path.exists():
pytest.skip( f"--gdal_path is required to point to the folder of the gdal python scripts (e.g. gdal_merge.py) " )

return gdal_path


5 changes: 5 additions & 0 deletions tests/test_hello.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from thaw_slump_segmentation.main import hello


def test_hello():
assert hello('Tobias') == 'Hello, Tobias!'
56 changes: 56 additions & 0 deletions tests/test_preprocessDirectory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import pytest
from pathlib import Path
import tempfile, shutil

from thaw_slump_segmentation.scripts.setup_raw_data import preprocess_directory


def testCompleteProcessing(data_dir:Path, gdal_bin, gdal_path):

# check if data_dir this is just a basic image folder formatted from planet download

image_dir = data_dir / "raw_data_dir" / "scenes" / "20230807_191420_44_241d"
aux_dir = data_dir / "auxiliary"
if not image_dir.exists():
pytest.skip(f"could not find predefined image dir {image_dir}")

# create a working directory and copy images there
temp_path = Path(tempfile.mkdtemp())

source_dir = temp_path / "input"
source_dir.mkdir()
[shutil.copy(f, source_dir) for f in image_dir.glob("*")]

# target directory of the preprocess_directory processing
target_dir = temp_path / "output"
target_dir.mkdir()

backup_dir = temp_path / "backup"
backup_dir.mkdir()

preprocess_result = preprocess_directory(
image_dir=source_dir,
data_dir=target_dir,
aux_dir=aux_dir,
backup_dir=backup_dir,
log_path=temp_path / "preprocess.log",
gdal_bin=gdal_bin, gdal_path=gdal_path, label_required=False
)

# {'rename': 2, 'label': 2, 'ndvi': 1, 'tcvis': 1, 'rel_dem': 1, 'slope': 1, 'mask': 1, 'move': 1}
# assert that every result entry except "rename" (was for chopping of _clip) and "label" is 1
for k,v in preprocess_result.items():
if k not in ["rename", "label"]:
assert v == 1

#assert that the files ndvi.tif, relative_elevation.tif, slope.tif and tcvis.tif exist in backup/input
# and their size is non-zero

for f in ["ndvi.tif", "relative_elevation.tif", "slope.tif", "tcvis.tif"]:
testfile = backup_dir / "input" / f
assert testfile.exists()
assert testfile.stat().st_size > 0


shutil.rmtree(temp_path)

0 comments on commit f79849c

Please sign in to comment.