File size: 7,185 Bytes
173dcbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
"""
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!")