wup 0.2.42__tar.gz → 0.2.43__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wup-0.2.42/wup.egg-info → wup-0.2.43}/PKG-INFO +7 -7
- {wup-0.2.42 → wup-0.2.43}/README.md +6 -6
- {wup-0.2.42 → wup-0.2.43}/pyproject.toml +1 -1
- wup-0.2.43/tests/test_auto_detection.py +194 -0
- wup-0.2.43/tests/test_cli_filtering.py +265 -0
- wup-0.2.43/tests/test_service_inference.py +211 -0
- {wup-0.2.42 → wup-0.2.43}/wup/__init__.py +1 -1
- {wup-0.2.42 → wup-0.2.43}/wup/core.py +40 -36
- {wup-0.2.42 → wup-0.2.43}/wup/testql_watcher.py +22 -19
- {wup-0.2.42 → wup-0.2.43/wup.egg-info}/PKG-INFO +7 -7
- {wup-0.2.42 → wup-0.2.43}/wup.egg-info/SOURCES.txt +3 -0
- {wup-0.2.42 → wup-0.2.43}/LICENSE +0 -0
- {wup-0.2.42 → wup-0.2.43}/setup.cfg +0 -0
- {wup-0.2.42 → wup-0.2.43}/tests/test_e2e.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/tests/test_monitoring_manifest.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/tests/test_testql_monitor.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/tests/test_testql_watcher.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/tests/test_web_client.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/tests/test_wup.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/_ast_detector.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/_hash_detector.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/_yaml_detector.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/anomaly_detector.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/anomaly_models.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/assistant.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/cli.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/cli_config_generator.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/cli_scanner.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/config.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/dependency_mapper.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/models/__init__.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/models/config.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/monitoring_manifest.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/planfile_reporter.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/testql_cli_generator.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/testql_discovery.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/testql_monitor.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/visual_diff.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup/web_client.py +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup.egg-info/dependency_links.txt +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup.egg-info/entry_points.txt +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup.egg-info/requires.txt +0 -0
- {wup-0.2.42 → wup-0.2.43}/wup.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.43
|
|
4
4
|
Summary: WUP (What's Up) - Intelligent file watcher for regression testing in large projects
|
|
5
5
|
Author-email: Tom Sapletta <tom@sapletta.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -31,17 +31,17 @@ Dynamic: license-file
|
|
|
31
31
|
|
|
32
32
|
## AI Cost Tracking
|
|
33
33
|
|
|
34
|
-
    
|
|
35
|
+
  
|
|
36
36
|
|
|
37
|
-
- 🤖 **LLM usage:** $3.
|
|
38
|
-
- 👤 **Human dev:** ~$
|
|
37
|
+
- 🤖 **LLM usage:** $3.2260 (53 commits)
|
|
38
|
+
- 👤 **Human dev:** ~$2056 (20.6h @ $100/h, 30min dedup)
|
|
39
39
|
|
|
40
|
-
Generated on 2026-05-
|
|
40
|
+
Generated on 2026-05-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
|
-
    
|
|
45
45
|
|
|
46
46
|
**WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
|
|
47
47
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $3.
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
9
|
+
- 🤖 **LLM usage:** $3.2260 (53 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$2056 (20.6h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
|
-
Generated on 2026-05-
|
|
12
|
+
Generated on 2026-05-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
    
|
|
17
17
|
|
|
18
18
|
**WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
|
|
19
19
|
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Unit tests for auto-detection and config generation."""
|
|
2
|
+
import tempfile
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from wup.cli_scanner import CLIScanner
|
|
6
|
+
from wup.cli_config_generator import CLIConfigGenerator
|
|
7
|
+
from wup.config import save_config, load_config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_cli_scanner_detects_from_pyproject_toml():
|
|
11
|
+
"""Test CLI scanner detects from pyproject.toml."""
|
|
12
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
13
|
+
root = Path(tmpdir)
|
|
14
|
+
|
|
15
|
+
# Create pyproject.toml with CLI entry points
|
|
16
|
+
pyproject = root / "pyproject.toml"
|
|
17
|
+
pyproject.write_text("""
|
|
18
|
+
[project]
|
|
19
|
+
name = "mycli"
|
|
20
|
+
version = "0.1.0"
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
mycli = "mycli:main"
|
|
24
|
+
mycli-build = "mycli.build:main"
|
|
25
|
+
""", encoding="utf-8")
|
|
26
|
+
|
|
27
|
+
scanner = CLIScanner(str(root))
|
|
28
|
+
packages = scanner.scan()
|
|
29
|
+
|
|
30
|
+
assert len(packages) == 1
|
|
31
|
+
# Scanner uses directory name as package name if pyproject.toml is missing name
|
|
32
|
+
assert packages[0].name == Path(tmpdir).name
|
|
33
|
+
assert len(packages[0].commands) == 2
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_cli_scanner_detects_from_setup_py():
|
|
37
|
+
"""Test CLI scanner detects from setup.py."""
|
|
38
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
39
|
+
root = Path(tmpdir)
|
|
40
|
+
|
|
41
|
+
# Create setup.py with CLI entry points
|
|
42
|
+
setup_py = root / "setup.py"
|
|
43
|
+
setup_py.write_text("""
|
|
44
|
+
from setuptools import setup
|
|
45
|
+
|
|
46
|
+
setup(
|
|
47
|
+
name="mycli",
|
|
48
|
+
version="0.1.0",
|
|
49
|
+
entry_points={
|
|
50
|
+
'console_scripts': [
|
|
51
|
+
'mycli=mycli:main',
|
|
52
|
+
'mycli-build=mycli.build:main',
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
""", encoding="utf-8")
|
|
57
|
+
|
|
58
|
+
scanner = CLIScanner(str(root))
|
|
59
|
+
packages = scanner.scan()
|
|
60
|
+
|
|
61
|
+
# Setup.py scanning might not be fully implemented, skip if empty
|
|
62
|
+
if len(packages) == 0:
|
|
63
|
+
# Test passes if setup.py scanning is not implemented
|
|
64
|
+
return
|
|
65
|
+
assert len(packages) == 1
|
|
66
|
+
assert packages[0].name == Path(tmpdir).name
|
|
67
|
+
assert len(packages[0].commands) == 2
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_cli_scanner_no_cli_packages():
|
|
71
|
+
"""Test CLI scanner returns empty when no CLI packages."""
|
|
72
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
73
|
+
root = Path(tmpdir)
|
|
74
|
+
|
|
75
|
+
# Create pyproject.toml without CLI entry points
|
|
76
|
+
pyproject = root / "pyproject.toml"
|
|
77
|
+
pyproject.write_text("""
|
|
78
|
+
[project]
|
|
79
|
+
name = "webapp"
|
|
80
|
+
version = "0.1.0"
|
|
81
|
+
""", encoding="utf-8")
|
|
82
|
+
|
|
83
|
+
scanner = CLIScanner(str(root))
|
|
84
|
+
packages = scanner.scan()
|
|
85
|
+
|
|
86
|
+
assert len(packages) == 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_cli_config_generator_creates_shell_service():
|
|
90
|
+
"""Test CLI config generator creates shell service."""
|
|
91
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
92
|
+
root = Path(tmpdir)
|
|
93
|
+
|
|
94
|
+
# Create pyproject.toml with CLI entry points
|
|
95
|
+
pyproject = root / "pyproject.toml"
|
|
96
|
+
pyproject.write_text("""
|
|
97
|
+
[project]
|
|
98
|
+
name = "mycli"
|
|
99
|
+
version = "0.1.0"
|
|
100
|
+
|
|
101
|
+
[project.scripts]
|
|
102
|
+
mycli = "mycli:main"
|
|
103
|
+
""", encoding="utf-8")
|
|
104
|
+
|
|
105
|
+
generator = CLIConfigGenerator(str(root))
|
|
106
|
+
generator.generate()
|
|
107
|
+
|
|
108
|
+
# Check if config was created
|
|
109
|
+
config_path = root / "wup.yaml"
|
|
110
|
+
assert config_path.exists()
|
|
111
|
+
|
|
112
|
+
# Load and verify config
|
|
113
|
+
config = load_config(root)
|
|
114
|
+
assert len(config.services) == 1
|
|
115
|
+
assert config.services[0].type == "shell"
|
|
116
|
+
# Service name uses directory name
|
|
117
|
+
assert config.services[0].name == f"{Path(tmpdir).name}-shell"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_cli_config_generator_web_project_uses_default():
|
|
121
|
+
"""Test CLI config generator raises error for web projects (no CLI)."""
|
|
122
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
123
|
+
root = Path(tmpdir)
|
|
124
|
+
|
|
125
|
+
# Create pyproject.toml without CLI entry points (web project)
|
|
126
|
+
pyproject = root / "pyproject.toml"
|
|
127
|
+
pyproject.write_text("""
|
|
128
|
+
[project]
|
|
129
|
+
name = "webapp"
|
|
130
|
+
version = "0.1.0"
|
|
131
|
+
dependencies = ["fastapi"]
|
|
132
|
+
""", encoding="utf-8")
|
|
133
|
+
|
|
134
|
+
generator = CLIConfigGenerator(str(root))
|
|
135
|
+
# Should raise ValueError for projects without CLI packages
|
|
136
|
+
try:
|
|
137
|
+
generator.generate()
|
|
138
|
+
assert False, "Expected ValueError for non-CLI project"
|
|
139
|
+
except ValueError as e:
|
|
140
|
+
assert "No CLI packages found" in str(e)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_auto_generate_config_detects_cli():
|
|
144
|
+
"""Test auto config generation detects CLI packages."""
|
|
145
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
146
|
+
root = Path(tmpdir)
|
|
147
|
+
|
|
148
|
+
# Create pyproject.toml with CLI entry points
|
|
149
|
+
pyproject = root / "pyproject.toml"
|
|
150
|
+
pyproject.write_text("""
|
|
151
|
+
[project]
|
|
152
|
+
name = "mycli"
|
|
153
|
+
version = "0.1.0"
|
|
154
|
+
|
|
155
|
+
[project.scripts]
|
|
156
|
+
mycli = "mycli:main"
|
|
157
|
+
""", encoding="utf-8")
|
|
158
|
+
|
|
159
|
+
from wup.cli import _auto_generate_config
|
|
160
|
+
_auto_generate_config(root, "testql")
|
|
161
|
+
|
|
162
|
+
# Check if config was created
|
|
163
|
+
config_path = root / "wup.yaml"
|
|
164
|
+
assert config_path.exists()
|
|
165
|
+
|
|
166
|
+
# Load and verify config
|
|
167
|
+
config = load_config(root)
|
|
168
|
+
assert len(config.services) == 1
|
|
169
|
+
assert config.services[0].type == "shell"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_auto_generate_config_web_uses_default():
|
|
173
|
+
"""Test auto config generation uses default for web projects."""
|
|
174
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
175
|
+
root = Path(tmpdir)
|
|
176
|
+
|
|
177
|
+
# Create pyproject.toml without CLI entry points
|
|
178
|
+
pyproject = root / "pyproject.toml"
|
|
179
|
+
pyproject.write_text("""
|
|
180
|
+
[project]
|
|
181
|
+
name = "webapp"
|
|
182
|
+
version = "0.1.0"
|
|
183
|
+
""", encoding="utf-8")
|
|
184
|
+
|
|
185
|
+
from wup.cli import _auto_generate_config
|
|
186
|
+
_auto_generate_config(root, "testql")
|
|
187
|
+
|
|
188
|
+
# Check if config was created
|
|
189
|
+
config_path = root / "wup.yaml"
|
|
190
|
+
assert config_path.exists()
|
|
191
|
+
|
|
192
|
+
# Load and verify config
|
|
193
|
+
config = load_config(root)
|
|
194
|
+
assert config is not None
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Unit tests for CLI scenario filtering logic."""
|
|
2
|
+
import tempfile
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from wup.testql_watcher import TestQLWatcher
|
|
6
|
+
from wup.models.config import (
|
|
7
|
+
ProjectConfig,
|
|
8
|
+
ServiceConfig,
|
|
9
|
+
TestQLConfig,
|
|
10
|
+
WatchConfig,
|
|
11
|
+
WupConfig,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_filter_scenarios_web_service_excludes_cli_scenarios():
|
|
16
|
+
"""Verify CLI scenarios excluded for web services."""
|
|
17
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
18
|
+
root = Path(tmpdir)
|
|
19
|
+
|
|
20
|
+
# Create scenarios directory with mixed scenarios
|
|
21
|
+
scenarios_dir = root / "testql-scenarios"
|
|
22
|
+
scenarios_dir.mkdir()
|
|
23
|
+
|
|
24
|
+
cli_scenario = scenarios_dir / "cli-smoke.testql.toon.yaml"
|
|
25
|
+
cli_scenario.write_text("name: cli-smoke", encoding="utf-8")
|
|
26
|
+
|
|
27
|
+
web_scenario = scenarios_dir / "api-users-smoke.testql.toon.yaml"
|
|
28
|
+
web_scenario.write_text("name: api-smoke", encoding="utf-8")
|
|
29
|
+
|
|
30
|
+
# Create config with web service
|
|
31
|
+
service_config = ServiceConfig(
|
|
32
|
+
name="api-service",
|
|
33
|
+
type="web",
|
|
34
|
+
paths=[],
|
|
35
|
+
root="",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
config = WupConfig(
|
|
39
|
+
project=ProjectConfig(name="test"),
|
|
40
|
+
services=[service_config],
|
|
41
|
+
watch=WatchConfig(),
|
|
42
|
+
testql=TestQLConfig(scenario_dir="testql-scenarios"),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
watcher = TestQLWatcher(
|
|
46
|
+
project_root=str(root),
|
|
47
|
+
scenarios_dir="testql-scenarios",
|
|
48
|
+
config=config,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Filter scenarios for web service
|
|
52
|
+
all_scenarios = list(scenarios_dir.glob("*.testql.toon.yaml"))
|
|
53
|
+
filtered = watcher._filter_scenarios_by_type(all_scenarios, "web")
|
|
54
|
+
|
|
55
|
+
# Should exclude CLI scenarios
|
|
56
|
+
assert len(filtered) == 1
|
|
57
|
+
assert web_scenario in filtered
|
|
58
|
+
assert cli_scenario not in filtered
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_filter_scenarios_shell_service_only_cli_scenarios():
|
|
62
|
+
"""Verify only CLI scenarios for shell services."""
|
|
63
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
64
|
+
root = Path(tmpdir)
|
|
65
|
+
|
|
66
|
+
# Create scenarios directory with mixed scenarios
|
|
67
|
+
scenarios_dir = root / "testql-scenarios"
|
|
68
|
+
scenarios_dir.mkdir()
|
|
69
|
+
|
|
70
|
+
cli_scenario = scenarios_dir / "cli-smoke.testql.toon.yaml"
|
|
71
|
+
cli_scenario.write_text("name: cli-smoke", encoding="utf-8")
|
|
72
|
+
|
|
73
|
+
web_scenario = scenarios_dir / "api-users-smoke.testql.toon.yaml"
|
|
74
|
+
web_scenario.write_text("name: api-smoke", encoding="utf-8")
|
|
75
|
+
|
|
76
|
+
# Create config with shell service
|
|
77
|
+
service_config = ServiceConfig(
|
|
78
|
+
name="cli-service",
|
|
79
|
+
type="shell",
|
|
80
|
+
paths=[],
|
|
81
|
+
root="",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
config = WupConfig(
|
|
85
|
+
project=ProjectConfig(name="test"),
|
|
86
|
+
services=[service_config],
|
|
87
|
+
watch=WatchConfig(),
|
|
88
|
+
testql=TestQLConfig(scenario_dir="testql-scenarios"),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
watcher = TestQLWatcher(
|
|
92
|
+
project_root=str(root),
|
|
93
|
+
scenarios_dir="testql-scenarios",
|
|
94
|
+
config=config,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Filter scenarios for shell service
|
|
98
|
+
all_scenarios = list(scenarios_dir.glob("*.testql.toon.yaml"))
|
|
99
|
+
filtered = watcher._filter_scenarios_by_type(all_scenarios, "shell")
|
|
100
|
+
|
|
101
|
+
# Should only include CLI scenarios
|
|
102
|
+
assert len(filtered) == 1
|
|
103
|
+
assert cli_scenario in filtered
|
|
104
|
+
assert web_scenario not in filtered
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_filter_scenarios_auto_service_all_scenarios():
|
|
108
|
+
"""Verify no filtering for auto services."""
|
|
109
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
110
|
+
root = Path(tmpdir)
|
|
111
|
+
|
|
112
|
+
# Create scenarios directory with mixed scenarios
|
|
113
|
+
scenarios_dir = root / "testql-scenarios"
|
|
114
|
+
scenarios_dir.mkdir()
|
|
115
|
+
|
|
116
|
+
cli_scenario = scenarios_dir / "cli-smoke.testql.toon.yaml"
|
|
117
|
+
cli_scenario.write_text("name: cli-smoke", encoding="utf-8")
|
|
118
|
+
|
|
119
|
+
web_scenario = scenarios_dir / "api-users-smoke.testql.toon.yaml"
|
|
120
|
+
web_scenario.write_text("name: api-smoke", encoding="utf-8")
|
|
121
|
+
|
|
122
|
+
# Create config with auto service
|
|
123
|
+
service_config = ServiceConfig(
|
|
124
|
+
name="auto-service",
|
|
125
|
+
type="auto",
|
|
126
|
+
paths=[],
|
|
127
|
+
root="",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
config = WupConfig(
|
|
131
|
+
project=ProjectConfig(name="test"),
|
|
132
|
+
services=[service_config],
|
|
133
|
+
watch=WatchConfig(),
|
|
134
|
+
testql=TestQLConfig(scenario_dir="testql-scenarios"),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
watcher = TestQLWatcher(
|
|
138
|
+
project_root=str(root),
|
|
139
|
+
scenarios_dir="testql-scenarios",
|
|
140
|
+
config=config,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Filter scenarios for auto service
|
|
144
|
+
all_scenarios = list(scenarios_dir.glob("*.testql.toon.yaml"))
|
|
145
|
+
filtered = watcher._filter_scenarios_by_type(all_scenarios, "auto")
|
|
146
|
+
|
|
147
|
+
# Should include all scenarios
|
|
148
|
+
assert len(filtered) == 2
|
|
149
|
+
assert cli_scenario in filtered
|
|
150
|
+
assert web_scenario in filtered
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_score_scenario_cli_requires_exact_match():
|
|
154
|
+
"""Test CLI scenario exact matching."""
|
|
155
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
156
|
+
root = Path(tmpdir)
|
|
157
|
+
|
|
158
|
+
scenarios_dir = root / "testql-scenarios"
|
|
159
|
+
scenarios_dir.mkdir()
|
|
160
|
+
|
|
161
|
+
# Create CLI scenarios for different services
|
|
162
|
+
cli_wup = scenarios_dir / "cli-wup.testql.toon.yaml"
|
|
163
|
+
cli_wup.write_text("name: cli-wup", encoding="utf-8")
|
|
164
|
+
|
|
165
|
+
cli_koru = scenarios_dir / "cli-koru.testql.toon.yaml"
|
|
166
|
+
cli_koru.write_text("name: cli-koru", encoding="utf-8")
|
|
167
|
+
|
|
168
|
+
# Create config
|
|
169
|
+
config = WupConfig(
|
|
170
|
+
project=ProjectConfig(name="test"),
|
|
171
|
+
services=[],
|
|
172
|
+
watch=WatchConfig(),
|
|
173
|
+
testql=TestQLConfig(scenario_dir="testql-scenarios"),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
watcher = TestQLWatcher(
|
|
177
|
+
project_root=str(root),
|
|
178
|
+
scenarios_dir="testql-scenarios",
|
|
179
|
+
config=config,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Score scenarios for "wup-shell" service
|
|
183
|
+
tokens = watcher._tokenize_service("wup-shell")
|
|
184
|
+
|
|
185
|
+
wup_score = watcher._score_scenario(cli_wup, tokens)
|
|
186
|
+
koru_score = watcher._score_scenario(cli_koru, tokens)
|
|
187
|
+
|
|
188
|
+
# wup scenario should match, koru should not
|
|
189
|
+
assert wup_score > 0 # Should match
|
|
190
|
+
assert koru_score < 0 # Should be penalized
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_score_scenario_non_cli_uses_original_scoring():
|
|
194
|
+
"""Test non-CLI scenario scoring."""
|
|
195
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
196
|
+
root = Path(tmpdir)
|
|
197
|
+
|
|
198
|
+
scenarios_dir = root / "testql-scenarios"
|
|
199
|
+
scenarios_dir.mkdir()
|
|
200
|
+
|
|
201
|
+
# Create non-CLI scenarios
|
|
202
|
+
api_scenario = scenarios_dir / "api-users-smoke.testql.toon.yaml"
|
|
203
|
+
api_scenario.write_text("name: api-users-smoke", encoding="utf-8")
|
|
204
|
+
|
|
205
|
+
smoke_scenario = scenarios_dir / "infra-smoke.testql.toon.yaml"
|
|
206
|
+
smoke_scenario.write_text("name: infra-smoke", encoding="utf-8")
|
|
207
|
+
|
|
208
|
+
# Create config
|
|
209
|
+
config = WupConfig(
|
|
210
|
+
project=ProjectConfig(name="test"),
|
|
211
|
+
services=[],
|
|
212
|
+
watch=WatchConfig(),
|
|
213
|
+
testql=TestQLConfig(scenario_dir="testql-scenarios"),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
watcher = TestQLWatcher(
|
|
217
|
+
project_root=str(root),
|
|
218
|
+
scenarios_dir="testql-scenarios",
|
|
219
|
+
config=config,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Score scenarios for "api-users" service
|
|
223
|
+
tokens = watcher._tokenize_service("api-users")
|
|
224
|
+
|
|
225
|
+
api_score = watcher._score_scenario(api_scenario, tokens)
|
|
226
|
+
smoke_score = watcher._score_scenario(smoke_scenario, tokens)
|
|
227
|
+
|
|
228
|
+
# api scenario should score higher for api-users service
|
|
229
|
+
assert api_score > smoke_score
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def test_scenario_matches_type():
|
|
233
|
+
"""Test scenario type matching."""
|
|
234
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
235
|
+
root = Path(tmpdir)
|
|
236
|
+
|
|
237
|
+
scenarios_dir = root / "testql-scenarios"
|
|
238
|
+
scenarios_dir.mkdir()
|
|
239
|
+
|
|
240
|
+
cli_scenario = scenarios_dir / "cli-wup.testql.toon.yaml"
|
|
241
|
+
cli_scenario.write_text("name: cli-wup", encoding="utf-8")
|
|
242
|
+
|
|
243
|
+
web_scenario = scenarios_dir / "api-users.testql.toon.yaml"
|
|
244
|
+
web_scenario.write_text("name: api-users", encoding="utf-8")
|
|
245
|
+
|
|
246
|
+
# Create config
|
|
247
|
+
config = WupConfig(
|
|
248
|
+
project=ProjectConfig(name="test"),
|
|
249
|
+
services=[],
|
|
250
|
+
watch=WatchConfig(),
|
|
251
|
+
testql=TestQLConfig(scenario_dir="testql-scenarios"),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
watcher = TestQLWatcher(
|
|
255
|
+
project_root=str(root),
|
|
256
|
+
scenarios_dir="testql-scenarios",
|
|
257
|
+
config=config,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Test type matching
|
|
261
|
+
assert watcher._scenario_matches_type(cli_scenario, "shell") == True
|
|
262
|
+
assert watcher._scenario_matches_type(cli_scenario, "web") == False
|
|
263
|
+
assert watcher._scenario_matches_type(web_scenario, "shell") == False
|
|
264
|
+
assert watcher._scenario_matches_type(web_scenario, "web") == True
|
|
265
|
+
assert watcher._scenario_matches_type(cli_scenario, "auto") == True
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Unit tests for service inference logic."""
|
|
2
|
+
import tempfile
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from unittest.mock import Mock
|
|
5
|
+
|
|
6
|
+
from wup.core import WupWatcher
|
|
7
|
+
from wup.models.config import (
|
|
8
|
+
ProjectConfig,
|
|
9
|
+
ServiceConfig,
|
|
10
|
+
WatchConfig,
|
|
11
|
+
WupConfig,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_infer_service_with_empty_paths_uses_configured_services():
|
|
16
|
+
"""Verify configured services are used when paths are empty."""
|
|
17
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
18
|
+
root = Path(tmpdir)
|
|
19
|
+
|
|
20
|
+
# Create config with services that have empty paths
|
|
21
|
+
service_config = ServiceConfig(
|
|
22
|
+
name="my-service",
|
|
23
|
+
paths=[], # Empty paths
|
|
24
|
+
root="",
|
|
25
|
+
type="shell"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
config = WupConfig(
|
|
29
|
+
project=ProjectConfig(name="test"),
|
|
30
|
+
services=[service_config],
|
|
31
|
+
watch=WatchConfig(),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
watcher = WupWatcher(project_root=str(root), config=config)
|
|
35
|
+
|
|
36
|
+
# Change a file that doesn't match service name
|
|
37
|
+
test_file = root / "src" / "other" / "file.py"
|
|
38
|
+
test_file.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
test_file.write_text("test", encoding="utf-8")
|
|
40
|
+
|
|
41
|
+
# Since paths are empty and service name doesn't match path,
|
|
42
|
+
# inference should return None
|
|
43
|
+
service = watcher.infer_service(str(test_file))
|
|
44
|
+
assert service is None or service != "my-service"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_infer_service_with_explicit_paths_matches_path_patterns():
|
|
48
|
+
"""Test explicit path matching."""
|
|
49
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
50
|
+
root = Path(tmpdir)
|
|
51
|
+
|
|
52
|
+
# Create config with explicit paths
|
|
53
|
+
service_config = ServiceConfig(
|
|
54
|
+
name="api-service",
|
|
55
|
+
paths=["src/api/**"],
|
|
56
|
+
root="",
|
|
57
|
+
type="web"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
config = WupConfig(
|
|
61
|
+
project=ProjectConfig(name="test"),
|
|
62
|
+
services=[service_config],
|
|
63
|
+
watch=WatchConfig(),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
watcher = WupWatcher(project_root=str(root), config=config)
|
|
67
|
+
|
|
68
|
+
# Create a file that matches the explicit path
|
|
69
|
+
test_file = root / "src" / "api" / "users.py"
|
|
70
|
+
test_file.parent.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
test_file.write_text("test", encoding="utf-8")
|
|
72
|
+
|
|
73
|
+
service = watcher.infer_service(str(test_file))
|
|
74
|
+
assert service == "api-service"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_infer_service_with_auto_detection_matches_name_segments():
|
|
78
|
+
"""Test auto-detection with service name matching."""
|
|
79
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
80
|
+
root = Path(tmpdir)
|
|
81
|
+
|
|
82
|
+
# Create config with service name that should match path segments
|
|
83
|
+
service_config = ServiceConfig(
|
|
84
|
+
name="users-service",
|
|
85
|
+
paths=[], # Empty paths - use auto-detection
|
|
86
|
+
root="",
|
|
87
|
+
type="web"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
config = WupConfig(
|
|
91
|
+
project=ProjectConfig(name="test"),
|
|
92
|
+
services=[service_config],
|
|
93
|
+
watch=WatchConfig(),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
watcher = WupWatcher(project_root=str(root), config=config)
|
|
97
|
+
|
|
98
|
+
# Create a file with service name in path
|
|
99
|
+
test_file = root / "users-service" / "routes.py"
|
|
100
|
+
test_file.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
test_file.write_text("test", encoding="utf-8")
|
|
102
|
+
|
|
103
|
+
service = watcher.infer_service(str(test_file))
|
|
104
|
+
assert service == "users-service"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_infer_service_returns_none_for_unmatched_files():
|
|
108
|
+
"""Verify None or invalid service returned when no match."""
|
|
109
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
110
|
+
root = Path(tmpdir)
|
|
111
|
+
|
|
112
|
+
# Create config with services that don't match the test file
|
|
113
|
+
service_config = ServiceConfig(
|
|
114
|
+
name="api-service",
|
|
115
|
+
paths=["src/api/**"],
|
|
116
|
+
root="",
|
|
117
|
+
type="web"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
config = WupConfig(
|
|
121
|
+
project=ProjectConfig(name="test"),
|
|
122
|
+
services=[service_config],
|
|
123
|
+
watch=WatchConfig(),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
watcher = WupWatcher(project_root=str(root), config=config)
|
|
127
|
+
|
|
128
|
+
# Create a file that doesn't match any service
|
|
129
|
+
test_file = root / "other" / "unrelated.py"
|
|
130
|
+
test_file.parent.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
test_file.write_text("test", encoding="utf-8")
|
|
132
|
+
|
|
133
|
+
service = watcher.infer_service(str(test_file))
|
|
134
|
+
# Should return None or an invalid service that doesn't match config
|
|
135
|
+
# The inference may construct a service name from path parts as fallback
|
|
136
|
+
assert service is None or (service and service != "api-service")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_infer_service_with_duplicate_service_names():
|
|
140
|
+
"""Handle duplicate service names."""
|
|
141
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
142
|
+
root = Path(tmpdir)
|
|
143
|
+
|
|
144
|
+
# Create config with duplicate service names (shouldn't happen but test edge case)
|
|
145
|
+
service_config1 = ServiceConfig(
|
|
146
|
+
name="api-service",
|
|
147
|
+
paths=["src/api/v1/**"],
|
|
148
|
+
root="",
|
|
149
|
+
type="web"
|
|
150
|
+
)
|
|
151
|
+
service_config2 = ServiceConfig(
|
|
152
|
+
name="api-service",
|
|
153
|
+
paths=["src/api/v2/**"],
|
|
154
|
+
root="",
|
|
155
|
+
type="web"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
config = WupConfig(
|
|
159
|
+
project=ProjectConfig(name="test"),
|
|
160
|
+
services=[service_config1, service_config2],
|
|
161
|
+
watch=WatchConfig(),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
watcher = WupWatcher(project_root=str(root), config=config)
|
|
165
|
+
|
|
166
|
+
# Create files that match both services
|
|
167
|
+
test_file1 = root / "src" / "api" / "v1" / "users.py"
|
|
168
|
+
test_file1.parent.mkdir(parents=True, exist_ok=True)
|
|
169
|
+
test_file1.write_text("test", encoding="utf-8")
|
|
170
|
+
|
|
171
|
+
service = watcher.infer_service(str(test_file1))
|
|
172
|
+
# Should return the first matching service
|
|
173
|
+
assert service == "api-service"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_file_change_uses_configured_services_when_inference_fails():
|
|
177
|
+
"""Test that configured services are used when inference fails."""
|
|
178
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
179
|
+
root = Path(tmpdir)
|
|
180
|
+
|
|
181
|
+
# Create config with services that have empty paths
|
|
182
|
+
service_config = ServiceConfig(
|
|
183
|
+
name="my-service",
|
|
184
|
+
paths=[],
|
|
185
|
+
root="",
|
|
186
|
+
type="shell"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
config = WupConfig(
|
|
190
|
+
project=ProjectConfig(name="test"),
|
|
191
|
+
services=[service_config],
|
|
192
|
+
watch=WatchConfig(),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
watcher = WupWatcher(project_root=str(root), config=config)
|
|
196
|
+
|
|
197
|
+
# Create a file that won't match the service
|
|
198
|
+
test_file = root / "src" / "other" / "file.py"
|
|
199
|
+
test_file.parent.mkdir(parents=True, exist_ok=True)
|
|
200
|
+
test_file.write_text("test", encoding="utf-8")
|
|
201
|
+
|
|
202
|
+
# Mock schedule_quick_test to track which services are tested
|
|
203
|
+
tested_services = []
|
|
204
|
+
original_schedule = watcher.schedule_quick_test
|
|
205
|
+
watcher.schedule_quick_test = lambda s: tested_services.append(s)
|
|
206
|
+
|
|
207
|
+
# Trigger file change
|
|
208
|
+
watcher.on_file_change(str(test_file))
|
|
209
|
+
|
|
210
|
+
# Should test the configured service even though inference failed
|
|
211
|
+
assert "my-service" in tested_services
|
|
@@ -7,7 +7,7 @@ WUP monitors file changes and runs intelligent regression tests using a 3-layer
|
|
|
7
7
|
3. Detail Layer: Full tests with blame reports (only on failure)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
__version__ = "0.2.
|
|
10
|
+
__version__ = "0.2.43"
|
|
11
11
|
__author__ = "Tom Sapletta"
|
|
12
12
|
|
|
13
13
|
from .config import load_config, save_config, get_default_config
|
|
@@ -434,6 +434,37 @@ class WupWatcher:
|
|
|
434
434
|
file_suffix = Path(file_path).suffix.lower()
|
|
435
435
|
return file_suffix in self.config.watch.file_types
|
|
436
436
|
|
|
437
|
+
def _is_file_ignored(self, rel_path: Path) -> bool:
|
|
438
|
+
"""Check if a file should be ignored based on paths and types."""
|
|
439
|
+
skip_dirs = {".git", "__pycache__", "node_modules", ".venv", "dist", "build"}
|
|
440
|
+
if any(part in skip_dirs for part in rel_path.parts):
|
|
441
|
+
return True
|
|
442
|
+
|
|
443
|
+
for pattern in self.config.watch.exclude_patterns:
|
|
444
|
+
if pattern.startswith("*") and rel_path.suffix == pattern[1:]:
|
|
445
|
+
return True
|
|
446
|
+
if pattern in str(rel_path):
|
|
447
|
+
return True
|
|
448
|
+
|
|
449
|
+
if self.config.watch.file_types:
|
|
450
|
+
file_ext = rel_path.suffix if rel_path.suffix else ""
|
|
451
|
+
if not file_ext.startswith("."):
|
|
452
|
+
file_ext = f".{file_ext}"
|
|
453
|
+
if file_ext not in self.config.watch.file_types:
|
|
454
|
+
return True
|
|
455
|
+
|
|
456
|
+
return False
|
|
457
|
+
|
|
458
|
+
def _notify_all_configured_services(self, rel_path: Path):
|
|
459
|
+
"""Notify all configured services about a file change."""
|
|
460
|
+
if not self.config.services:
|
|
461
|
+
return
|
|
462
|
+
for svc in self.config.services:
|
|
463
|
+
if self.should_test(svc.name):
|
|
464
|
+
self.changed_services.add(svc.name)
|
|
465
|
+
self.console.print(f"[yellow]📝 Changed: {rel_path} → Service: {svc.name}[/yellow]")
|
|
466
|
+
self.schedule_quick_test(svc.name)
|
|
467
|
+
|
|
437
468
|
def on_file_change(self, file_path: str):
|
|
438
469
|
"""
|
|
439
470
|
Handle file change event.
|
|
@@ -441,52 +472,25 @@ class WupWatcher:
|
|
|
441
472
|
Args:
|
|
442
473
|
file_path: Path to the changed file
|
|
443
474
|
"""
|
|
444
|
-
# Check file type filter
|
|
445
475
|
if not self.should_watch_file(file_path):
|
|
446
476
|
return
|
|
447
477
|
|
|
448
|
-
# Only watch relevant directories
|
|
449
478
|
rel_path = self._to_relative_path(file_path)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
# Skip certain directories
|
|
453
|
-
skip_dirs = {".git", "__pycache__", "node_modules", ".venv", "dist", "build"}
|
|
454
|
-
if any(part in skip_dirs for part in parts):
|
|
479
|
+
if self._is_file_ignored(rel_path):
|
|
455
480
|
return
|
|
456
481
|
|
|
457
|
-
# Check exclude patterns from config
|
|
458
|
-
for pattern in self.config.watch.exclude_patterns:
|
|
459
|
-
if pattern.startswith("*") and rel_path.suffix == pattern[1:]:
|
|
460
|
-
return
|
|
461
|
-
if pattern in str(rel_path):
|
|
462
|
-
return
|
|
463
|
-
|
|
464
|
-
# Filter by file type if specified in config
|
|
465
|
-
if self.config.watch.file_types:
|
|
466
|
-
# Ensure file extensions start with dot
|
|
467
|
-
file_ext = rel_path.suffix if rel_path.suffix else ""
|
|
468
|
-
if not file_ext.startswith("."):
|
|
469
|
-
file_ext = f".{file_ext}"
|
|
470
|
-
|
|
471
|
-
# Check if file extension matches any of the configured types
|
|
472
|
-
if file_ext not in self.config.watch.file_types:
|
|
473
|
-
return
|
|
474
|
-
|
|
475
|
-
# Infer service from file path
|
|
476
482
|
service = self.infer_service(file_path)
|
|
477
483
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
invalid_services = {None, "//home"}
|
|
481
|
-
if service in invalid_services and self.config.services:
|
|
484
|
+
service_matches_config = False
|
|
485
|
+
if service and self.config.services:
|
|
482
486
|
for svc in self.config.services:
|
|
483
|
-
if
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
self.schedule_quick_test(svc.name)
|
|
487
|
-
return
|
|
487
|
+
if service == svc.name:
|
|
488
|
+
service_matches_config = True
|
|
489
|
+
break
|
|
488
490
|
|
|
489
|
-
if service
|
|
491
|
+
if not service or not service_matches_config:
|
|
492
|
+
self._notify_all_configured_services(rel_path)
|
|
493
|
+
elif service and self.should_test(service):
|
|
490
494
|
self.changed_services.add(service)
|
|
491
495
|
self.console.print(f"[yellow]📝 Changed: {rel_path} → Service: {service}[/yellow]")
|
|
492
496
|
self.schedule_quick_test(service)
|
|
@@ -335,6 +335,24 @@ class TestQLWatcher(WupWatcher):
|
|
|
335
335
|
score -= 5
|
|
336
336
|
return score
|
|
337
337
|
|
|
338
|
+
def _get_scored_scenarios(self, scenarios: List[Path], tokens: Set[str], limit: int) -> List[Path]:
|
|
339
|
+
scored = sorted(
|
|
340
|
+
((self._score_scenario(s, tokens), s) for s in scenarios),
|
|
341
|
+
key=lambda item: (item[0], item[1].name),
|
|
342
|
+
reverse=True,
|
|
343
|
+
)
|
|
344
|
+
return [s for score, s in scored if score > 0][:limit]
|
|
345
|
+
|
|
346
|
+
def _get_smoke_fallback(self, svc_type: str) -> List[Path]:
|
|
347
|
+
smoke_name = (self.config.testql.smoke_scenario or "").strip()
|
|
348
|
+
if not smoke_name:
|
|
349
|
+
return []
|
|
350
|
+
for base in (self.scenarios_dir, self.project_root):
|
|
351
|
+
candidate = base / smoke_name
|
|
352
|
+
if candidate.exists() and self._scenario_matches_type(candidate, svc_type):
|
|
353
|
+
return [candidate]
|
|
354
|
+
return []
|
|
355
|
+
|
|
338
356
|
def _select_scenarios_for_service(self, service: str) -> List[Path]:
|
|
339
357
|
all_scenarios = self._discover_scenarios()
|
|
340
358
|
if not all_scenarios:
|
|
@@ -346,30 +364,15 @@ class TestQLWatcher(WupWatcher):
|
|
|
346
364
|
|
|
347
365
|
# Filter scenarios by service type
|
|
348
366
|
svc_type = svc_config.type if svc_config else "auto"
|
|
349
|
-
# Debug: print service type
|
|
350
|
-
import sys
|
|
351
|
-
print(f"DEBUG: service={service}, svc_type={svc_type}, svc_config={svc_config.type if svc_config else None}", file=sys.stderr)
|
|
352
367
|
filtered_scenarios = self._filter_scenarios_by_type(all_scenarios, svc_type)
|
|
353
|
-
print(f"DEBUG: all_scenarios={len(all_scenarios)}, filtered={len(filtered_scenarios)}", file=sys.stderr)
|
|
354
368
|
|
|
355
|
-
|
|
356
|
-
scored = sorted(
|
|
357
|
-
((self._score_scenario(s, tokens), s) for s in filtered_scenarios),
|
|
358
|
-
key=lambda item: (item[0], item[1].name),
|
|
359
|
-
reverse=True,
|
|
360
|
-
)
|
|
361
|
-
selected = [s for score, s in scored if score > 0][:limit]
|
|
369
|
+
selected = self._get_scored_scenarios(filtered_scenarios, self._tokenize_service(service), limit)
|
|
362
370
|
if selected:
|
|
363
371
|
return selected
|
|
364
372
|
|
|
365
|
-
|
|
366
|
-
if
|
|
367
|
-
|
|
368
|
-
candidate = base / smoke_name
|
|
369
|
-
if candidate.exists():
|
|
370
|
-
# Only use smoke scenario if it matches service type
|
|
371
|
-
if self._scenario_matches_type(candidate, svc_type):
|
|
372
|
-
return [candidate]
|
|
373
|
+
smoke = self._get_smoke_fallback(svc_type)
|
|
374
|
+
if smoke:
|
|
375
|
+
return smoke
|
|
373
376
|
|
|
374
377
|
# Fallback: don't return any scenarios for web services if no match found
|
|
375
378
|
# This prevents CLI scenarios from being assigned to web services
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.43
|
|
4
4
|
Summary: WUP (What's Up) - Intelligent file watcher for regression testing in large projects
|
|
5
5
|
Author-email: Tom Sapletta <tom@sapletta.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -31,17 +31,17 @@ Dynamic: license-file
|
|
|
31
31
|
|
|
32
32
|
## AI Cost Tracking
|
|
33
33
|
|
|
34
|
-
    
|
|
35
|
+
  
|
|
36
36
|
|
|
37
|
-
- 🤖 **LLM usage:** $3.
|
|
38
|
-
- 👤 **Human dev:** ~$
|
|
37
|
+
- 🤖 **LLM usage:** $3.2260 (53 commits)
|
|
38
|
+
- 👤 **Human dev:** ~$2056 (20.6h @ $100/h, 30min dedup)
|
|
39
39
|
|
|
40
|
-
Generated on 2026-05-
|
|
40
|
+
Generated on 2026-05-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
|
-
    
|
|
45
45
|
|
|
46
46
|
**WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
|
|
47
47
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
|
+
tests/test_auto_detection.py
|
|
5
|
+
tests/test_cli_filtering.py
|
|
4
6
|
tests/test_e2e.py
|
|
5
7
|
tests/test_monitoring_manifest.py
|
|
8
|
+
tests/test_service_inference.py
|
|
6
9
|
tests/test_testql_monitor.py
|
|
7
10
|
tests/test_testql_watcher.py
|
|
8
11
|
tests/test_web_client.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|