imcui / tests /test_cli_app.py
vggt's picture
init
173dcbd
Raw
History Blame Contribute Delete
7.19 kB
"""
Test cases for CLI and app.py functionality.
"""
import os
import sys
import tempfile
import subprocess
from pathlib import Path
import pytest
import yaml
from loguru import logger
# Add the parent directory to Python path
ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(ROOT))
TIMEOUT = 10 # seconds
@pytest.mark.skip(reason="Skipping due to occasional CI timeouts")
def test_cli_help():
"""Test that CLI help command works."""
result = subprocess.run(
[sys.executable, "-m", "imcui.cli.main", "--help"],
capture_output=True,
text=True,
cwd=ROOT,
)
assert result.returncode == 0
assert "Launch the Image Matching WebUI application" in result.stdout
assert "--server-port" in result.stdout
assert "--config" in result.stdout
logger.info("CLI help command works as expected.")
def test_cli_version():
"""Test that CLI version command works."""
result = subprocess.run(
[sys.executable, "-m", "imcui.cli.main", "--version"],
capture_output=True,
text=True,
cwd=ROOT,
)
assert result.returncode == 0
assert "Image Matching WebUI Version" in result.stdout
logger.info("CLI version command works as expected.")
def test_cli_default_config_loading():
"""Test that CLI can load default configuration."""
from imcui import get_default_config_path
config_path = get_default_config_path()
assert config_path.exists(), f"Config file not found: {config_path}"
# Load and validate config structure
with open(config_path, "r") as f:
config = yaml.safe_load(f)
assert "server" in config
assert "defaults" in config
logger.info("CLI default configuration loaded and validated successfully.")
@pytest.mark.skip(reason="Skipping due to occasional CI timeouts")
def test_app_py_default_config():
"""Test that app.py can load default configuration."""
# Test the config path resolution in app.py
import argparse
# Mock the argument parsing to test config path
# app.py now checks current dir first, then falls back to package default
parser = argparse.ArgumentParser()
parser.add_argument(
"--config", type=str, default=str(ROOT / "imcui/config/app.yaml")
)
args = parser.parse_args([])
config_path = Path(args.config)
assert config_path.exists(), f"Config file not found: {config_path}"
logger.info(f"Default config path: {config_path}")
# Load and validate config
with open(config_path, "r") as f:
config = yaml.safe_load(f)
assert "server" in config
# matcher_zoo is now dynamically loaded from vismatch, not in config
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
log_path = temp_path / "experiments" / "all"
log_path.mkdir(parents=True, exist_ok=True)
# Update config to use temporary log path to avoid writing to repo
config["log_path"] = str(log_path)
# Save modified config to temp file
temp_config = temp_path / "temp_config.yaml"
with open(temp_config, "w") as f:
yaml.dump(config, f)
# Run app.py with the temporary config to ensure no errors
result = subprocess.run(
[sys.executable, "app.py", "--config", str(temp_config)],
capture_output=True,
text=True,
cwd=ROOT,
timeout=TIMEOUT, # Increase timeout to account for initialization
)
# We expect the server to start but then timeout when trying to run
assert result.returncode != 0 or "Running on" in result.stdout
logger.info("app.py ran successfully with temporary config.")
def test_cli_with_custom_config():
"""Test CLI with custom configuration file."""
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
custom_config = temp_path / "test_config.yaml"
# Create a minimal valid config with only basic matchers to avoid slow loading
config_data = {
"server": {"name": "127.0.0.1", "port": 9999},
"defaults": {"max_keypoints": 1000, "match_threshold": 0.1},
"matcher_zoo": {
"sift": {
"enable": True,
"matcher": "NN-mutual",
"feature": "sift",
"dense": False,
"info": {"name": "SIFT", "source": "IJCV 2004", "display": True},
}
},
}
with open(custom_config, "w") as f:
yaml.dump(config_data, f)
# Test that CLI can load the custom config
from click.testing import CliRunner
from imcui.cli.main import main as cli_main
runner = CliRunner()
result = runner.invoke(
cli_main,
["--config", str(custom_config), "--verbose"],
env={"IMCUI_CLI_TEST": "1"},
)
# Ensure it at least parsed the config and printed the config file path.
assert "Config file:" in result.output
logger.info("CLI successfully loaded and parsed custom config.")
def test_package_entry_point():
"""Test that the package entry point works."""
# Test that the package can be imported and has the expected structure
try:
import imcui
from imcui.cli import main as cli_main
from imcui.ui import ImageMatchingApp
# Verify key components exist
assert hasattr(imcui, "__version__") or hasattr(imcui, "__name__")
assert callable(cli_main.main)
assert hasattr(ImageMatchingApp, "run")
except ImportError as e:
pytest.fail(f"Failed to import package components: {e}")
logger.info("Package entry point and components are valid.")
@pytest.mark.skipif(
os.name == "nt", reason="Skip on Windows due to signal handling issues"
)
def test_cli_quick_exit():
"""Test that CLI exits quickly when interrupted."""
import signal
import time
# Start the CLI process
process = subprocess.Popen(
[sys.executable, "-m", "imcui.cli.main", "--server-port", "0"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=ROOT,
)
# Give it a moment to start
time.sleep(1)
# Send interrupt signal
process.send_signal(signal.SIGINT)
# Wait for process to terminate
try:
stdout, stderr = process.communicate(timeout=5)
assert (
process.returncode != 0
) # Should exit with non-zero code due to interrupt
except subprocess.TimeoutExpired:
process.kill()
pytest.fail("CLI process did not exit promptly after interrupt")
logger.info("CLI process exited promptly after interrupt.")
if __name__ == "__main__":
# Run tests manually for debugging
test_cli_help()
test_cli_version()
test_cli_default_config_loading()
# Skip test_app_py_default_config when running directly - requires long timeout
logger.info(
"Skipping test_app_py_default_config when running directly (use pytest)"
)
test_package_entry_point()
logger.success("All tests passed!")