Skip to content

Commit 149e5d2

Browse files
committed
Add integration tests
1 parent 60240c0 commit 149e5d2

4 files changed

Lines changed: 265 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Integration Tests
2+
3+
## Scope
4+
5+
Integration tests in this directory validate end-to-end model loading and inference through Model API wrappers.
6+
Result is not compared against reference outputs, but rather we check that the model can be loaded and run without exceptions on a sample, random input.
7+
8+
Current coverage is centered on `tests/integration/test_model.py`, which:
9+
10+
- Resolves model targets from CLI options (`--model-path` or HF Hub `--author`/`--collection`)
11+
- Loads models via:
12+
- `Model.create_model(...)` for local paths
13+
- `Model.from_pretrained(...)` for `hf://...` references
14+
- Runs inference on a deterministic random RGB image (`640x640x3`, `uint8`)
15+
- Passes if no exception is raised during load + single inference call
16+
17+
In short, this is a smoke/integration check for model usability, not an accuracy benchmark.
18+
19+
## Inputs and Parametrization
20+
21+
`pytest_generate_tests` in `tests/integration/test_model.py` dynamically parametrizes `path`:
22+
23+
1. If `--model-path` is provided:
24+
- `hf://...` => treated as a single HF model
25+
- file path => single local model
26+
- directory => recursively collects all `*.xml` files
27+
2. Else, if `--author` is set:
28+
- fetches model repos from HF Hub
29+
- optionally filtered by `--collection`
30+
3. If no paths are resolved:
31+
- test is skipped
32+
33+
## CLI Options
34+
35+
Defined in `tests/integration/conftest.py`:
36+
37+
- `--model-path` (default: `None`)
38+
Local file, local directory, or `hf://repo_id`
39+
- `--device` (default: `AUTO`)
40+
Target inference device, e.g. `CPU`, `GPU`, `AUTO`
41+
- `--author` (default: `OpenVINO`)
42+
HF Hub author/organization used when `--model-path` is not passed
43+
- `--collection` (default: `vision`)
44+
HF Hub collection under `author`
45+
46+
> Note: because `author`/`collection` have defaults, running this test without args attempts HF Hub discovery [OpenVINO/vision](https://huggingface.co/collections/OpenVINO/vision) unless you explicitly pass `--model-path`.
47+
48+
## Usage
49+
50+
Run from the `model_api` repository root.
51+
52+
### Default usage
53+
54+
When executed without any CLI options, the test will attempt to discover models from HF Hub [OpenVINO/vision](https://huggingface.co/collections/OpenVINO/vision) collection and run inference on all of those.
55+
56+
```bash
57+
uv --directory model_api run pytest tests/integration/test_model.py
58+
```
59+
60+
### All models from HF Hub author
61+
62+
```bash
63+
uv --directory model_api run pytest tests/integration/test_model.py --author hf_author_name
64+
```
65+
66+
### All models from HF Hub author and collection
67+
68+
```bash
69+
uv --directory model_api run pytest tests/integration/test_model.py --author hf_author_name --collection collection_name
70+
```
71+
72+
### Single model from HF Hub
73+
74+
```bash
75+
uv --directory model_api run pytest tests/integration/test_model.py --model-path hf://OpenVINO/resnet50-int8-ov
76+
```
77+
78+
79+
### Single local model with absolute path
80+
81+
```bash
82+
uv --directory model_api run pytest tests/integration/test_model.py --model-path /absolute/path/to/model.xml
83+
```
84+
85+
### Single local model with path relative to `model_api` package root
86+
87+
```bash
88+
uv --directory model_api run pytest tests/integration --model-path data/anomalib_models/padim.xml
89+
```
90+
91+
### Multiple local models from a directory
92+
93+
```bash
94+
uv --directory model_api run pytest tests/integration --model-path data/anomalib_models
95+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#
2+
# Copyright (C) 2020-2026 Intel Corporation
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#
2+
# Copyright (C) 2020-2026 Intel Corporation
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
6+
import pytest
7+
8+
9+
def pytest_addoption(parser):
10+
"""Add custom command-line options for integration tests."""
11+
parser.addoption(
12+
"--model-path",
13+
action="store",
14+
default=None,
15+
help="Path to model file or directory (local path or hf://repo/model). Optional.",
16+
)
17+
parser.addoption(
18+
"--device",
19+
action="store",
20+
default="AUTO",
21+
help="Inference device (e.g., CPU, GPU, AUTO). Defaults to AUTO.",
22+
)
23+
parser.addoption(
24+
"--author",
25+
action="store",
26+
default="OpenVINO",
27+
help="Hugging Face Hub author/organization name. Defaults to OpenVINO.",
28+
)
29+
parser.addoption(
30+
"--collection",
31+
action="store",
32+
default="vision",
33+
help="Hugging Face Hub collection name. Defaults to vision.",
34+
)
35+
36+
37+
@pytest.fixture(scope="session")
38+
def model_path(pytestconfig):
39+
"""Fixture to get the model path from command-line argument.
40+
41+
Returns:
42+
Optional[str]: Model path or None if not provided
43+
"""
44+
return pytestconfig.getoption("model_path")
45+
46+
47+
@pytest.fixture(scope="session")
48+
def device(pytestconfig):
49+
"""Fixture to get the device from command-line argument.
50+
51+
Returns:
52+
str: Inference device (defaults to "AUTO")
53+
"""
54+
return pytestconfig.getoption("device")
55+
56+
57+
@pytest.fixture(scope="session")
58+
def author(pytestconfig):
59+
"""Fixture to get the Hugging Face Hub author from command-line argument.
60+
61+
Returns:
62+
Optional[str]: Author/organization name or None if not provided
63+
"""
64+
return pytestconfig.getoption("author")
65+
66+
67+
@pytest.fixture(scope="session")
68+
def collection(pytestconfig):
69+
"""Fixture to get the Hugging Face Hub collection from command-line argument.
70+
71+
Returns:
72+
Optional[str]: Collection name or None if not provided
73+
"""
74+
return pytestconfig.getoption("collection")
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#
2+
# Copyright (C) 2020-2026 Intel Corporation
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
from pathlib import Path
6+
7+
import numpy as np
8+
import pytest
9+
from model_api.models.model import Model
10+
11+
rng = np.random.default_rng(seed=42)
12+
image = rng.integers(low=0, high=255, size=(640, 640, 3), dtype=np.uint8)
13+
14+
15+
def test_model(path, device) -> None:
16+
"""
17+
Test loading a model and performing inference on a random image.
18+
The test will pass if the model loads and runs without exceptions.
19+
"""
20+
model = get_model(path, device=device)
21+
22+
model(image)
23+
24+
assert True
25+
26+
27+
def pytest_generate_tests(metafunc):
28+
"""Parametrize the 'path' fixture based on command-line options."""
29+
if "path" in metafunc.fixturenames:
30+
model_path = metafunc.config.getoption("model_path")
31+
author = metafunc.config.getoption("author")
32+
collection = metafunc.config.getoption("collection")
33+
34+
paths = get_paths(model_path, author, collection)
35+
36+
if not paths:
37+
pytest.skip("No model path provided via --model-path or --author/--collection")
38+
39+
metafunc.parametrize("path", paths)
40+
41+
42+
def get_paths(model_path, author, collection) -> list[str]:
43+
"""
44+
Determine model paths based on command-line arguments.
45+
46+
Priority:
47+
1. If --model-path is provided, use it (can be a file, directory, or Hugging Face path).
48+
2. If --author is provided (with optional --collection), fetch model paths from Hugging Face Hub.
49+
3. If neither is provided, return an empty list.
50+
"""
51+
if model_path:
52+
if model_path.startswith("hf://"):
53+
return [model_path]
54+
55+
path = Path(model_path)
56+
if path.is_file():
57+
return [model_path]
58+
59+
if path.is_dir():
60+
return [str(p) for p in path.rglob("*.xml")]
61+
62+
msg = f"Invalid model path: {model_path}, expected a file, directory, or Hugging Face path starting with hf://"
63+
raise ValueError(msg)
64+
65+
if author:
66+
return get_model_paths_from_hf(author, collection)
67+
68+
return []
69+
70+
71+
def get_model(path: str, device: str) -> Model:
72+
"""Load a model from a given path, which can be a local file/directory or a Hugging Face Hub repository."""
73+
if path.startswith("hf://"):
74+
return Model.from_pretrained(path[5:], device=device)
75+
76+
return Model.create_model(path, device=device)
77+
78+
79+
def get_model_paths_from_hf(author: str, collection: str | None = None) -> list[str]:
80+
"""Fetch model paths from Hugging Face Hub based on author and optional collection in a format ''hf://{repo_id}''."""
81+
from huggingface_hub import HfApi, get_collection
82+
83+
api = HfApi()
84+
85+
if collection:
86+
return [
87+
f"hf://{item.item_id}"
88+
for item in get_collection(f"{author}/{collection}").items
89+
if item.item_type == "model"
90+
]
91+
92+
return [f"hf://{model.id}" for model in api.list_models(author=author)]

0 commit comments

Comments
 (0)