wup 0.2.45__tar.gz → 0.2.47__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.45/wup.egg-info → wup-0.2.47}/PKG-INFO +7 -7
- {wup-0.2.45 → wup-0.2.47}/README.md +6 -6
- {wup-0.2.45 → wup-0.2.47}/pyproject.toml +1 -1
- {wup-0.2.45 → wup-0.2.47}/tests/test_testql_watcher.py +38 -0
- {wup-0.2.45 → wup-0.2.47}/tests/test_wup.py +17 -0
- {wup-0.2.45 → wup-0.2.47}/wup/__init__.py +1 -1
- {wup-0.2.45 → wup-0.2.47}/wup/config.py +45 -1
- {wup-0.2.45 → wup-0.2.47}/wup/testql_watcher.py +34 -7
- {wup-0.2.45 → wup-0.2.47/wup.egg-info}/PKG-INFO +7 -7
- {wup-0.2.45 → wup-0.2.47}/LICENSE +0 -0
- {wup-0.2.45 → wup-0.2.47}/setup.cfg +0 -0
- {wup-0.2.45 → wup-0.2.47}/tests/test_auto_detection.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/tests/test_cli_filtering.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/tests/test_e2e.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/tests/test_monitoring_manifest.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/tests/test_service_inference.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/tests/test_testql_monitor.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/tests/test_web_client.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/_ast_detector.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/_base_detector.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/_hash_detector.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/_yaml_detector.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/anomaly_detector.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/anomaly_models.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/assistant.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/bus.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/cli.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/cli_config_generator.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/cli_scanner.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/core.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/dependency_mapper.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/event_store.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/file_watcher/events/file_events.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/models/__init__.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/models/config.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/monitoring_manifest.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/planfile_reporter.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/testing/events/health_events.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/testing/events/test_results.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/testing/handlers/event_handlers.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/testing/handlers/health_handlers.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/testing/queries/health_queries.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/testql_cli_generator.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/testql_discovery.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/testql_monitor.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/visual_diff.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup/web_client.py +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup.egg-info/SOURCES.txt +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup.egg-info/dependency_links.txt +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup.egg-info/entry_points.txt +0 -0
- {wup-0.2.45 → wup-0.2.47}/wup.egg-info/requires.txt +0 -0
- {wup-0.2.45 → wup-0.2.47}/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.47
|
|
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.4708 (57 commits)
|
|
38
|
+
- 👤 **Human dev:** ~$2304 (23.0h @ $100/h, 30min dedup)
|
|
39
39
|
|
|
40
|
-
Generated on 2026-05-
|
|
40
|
+
Generated on 2026-05-24 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.4708 (57 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$2304 (23.0h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
|
-
Generated on 2026-05-
|
|
12
|
+
Generated on 2026-05-24 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
|
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
+
import signal
|
|
4
5
|
import tempfile
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from subprocess import CompletedProcess
|
|
7
8
|
from unittest.mock import Mock
|
|
8
9
|
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
9
12
|
from wup.testql_watcher import TestQLWatcher
|
|
10
13
|
from wup.models.config import (
|
|
11
14
|
PlanfileConfig,
|
|
@@ -519,3 +522,38 @@ def test_quick_pass_actions_prefer_config_endpoints_for_visual_diff():
|
|
|
519
522
|
)
|
|
520
523
|
|
|
521
524
|
assert differ.calls == [("backend", ["http://localhost:8101/api/v3/health"])]
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def test_quick_interrupt_does_not_create_failure_track():
|
|
528
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
529
|
+
root = Path(tmpdir)
|
|
530
|
+
scenario_dir = root / "testql-scenarios"
|
|
531
|
+
scenario_dir.mkdir(parents=True, exist_ok=True)
|
|
532
|
+
(scenario_dir / "connect-config-smoke.testql.toon.yaml").write_text("name: smoke\n", encoding="utf-8")
|
|
533
|
+
|
|
534
|
+
cfg = WupConfig(
|
|
535
|
+
project=ProjectConfig(name="demo"),
|
|
536
|
+
services=[ServiceConfig(name="connect-config", paths=["connect-config/**"])],
|
|
537
|
+
watch=WatchConfig(),
|
|
538
|
+
testql=TestQLConfig(scenario_dir="testql-scenarios"),
|
|
539
|
+
)
|
|
540
|
+
watcher = TestQLWatcher(
|
|
541
|
+
project_root=str(root),
|
|
542
|
+
deps_file=str(root / "deps.json"),
|
|
543
|
+
scenarios_dir="testql-scenarios",
|
|
544
|
+
track_dir=".wup/tracks",
|
|
545
|
+
config=cfg,
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
watcher._run_testql = lambda args, timeout: CompletedProcess( # type: ignore[method-assign]
|
|
549
|
+
args=args,
|
|
550
|
+
returncode=-signal.SIGINT,
|
|
551
|
+
stdout="",
|
|
552
|
+
stderr="",
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
with pytest.raises(KeyboardInterrupt):
|
|
556
|
+
asyncio.run(watcher.run_quick_test("connect-config", []))
|
|
557
|
+
|
|
558
|
+
tracks = list((root / ".wup" / "tracks").glob("*.json"))
|
|
559
|
+
assert tracks == []
|
|
@@ -1345,6 +1345,23 @@ project:
|
|
|
1345
1345
|
with pytest.raises(ValueError, match="project.name"):
|
|
1346
1346
|
load_config(Path(tmpdir), config_path)
|
|
1347
1347
|
|
|
1348
|
+
def test_load_config_extra_args_normalization(self):
|
|
1349
|
+
"""Test that testql.extra_args parses space-separated strings and converts seconds to milliseconds."""
|
|
1350
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1351
|
+
config_content = """
|
|
1352
|
+
project:
|
|
1353
|
+
name: "test-project"
|
|
1354
|
+
testql:
|
|
1355
|
+
extra_args:
|
|
1356
|
+
- "--timeout 10s"
|
|
1357
|
+
- "--other-arg"
|
|
1358
|
+
- "--timeout=5s"
|
|
1359
|
+
"""
|
|
1360
|
+
config_path = Path(tmpdir) / "wup.yaml"
|
|
1361
|
+
config_path.write_text(config_content)
|
|
1362
|
+
config = load_config(Path(tmpdir), config_path)
|
|
1363
|
+
assert config.testql.extra_args == ["--timeout", "10000", "--other-arg", "--timeout=5000"]
|
|
1364
|
+
|
|
1348
1365
|
def test_save_and_load_visual_diff_config(self):
|
|
1349
1366
|
"""Test that visual_diff section is correctly saved and reloaded."""
|
|
1350
1367
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
@@ -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.47"
|
|
11
11
|
__author__ = "Tom Sapletta"
|
|
12
12
|
|
|
13
13
|
from .config import load_config, save_config, get_default_config
|
|
@@ -171,11 +171,55 @@ def validate_config(raw: dict) -> WupConfig:
|
|
|
171
171
|
|
|
172
172
|
# Parse testql config
|
|
173
173
|
testql_raw = raw.get("testql", {})
|
|
174
|
+
extra_args_raw = testql_raw.get("extra_args", ["--timeout", "10"])
|
|
175
|
+
extra_args = []
|
|
176
|
+
if isinstance(extra_args_raw, str):
|
|
177
|
+
extra_args_raw = extra_args_raw.split()
|
|
178
|
+
elif isinstance(extra_args_raw, list):
|
|
179
|
+
temp = []
|
|
180
|
+
for arg in extra_args_raw:
|
|
181
|
+
if isinstance(arg, str):
|
|
182
|
+
temp.extend(arg.split())
|
|
183
|
+
else:
|
|
184
|
+
temp.append(str(arg))
|
|
185
|
+
extra_args_raw = temp
|
|
186
|
+
else:
|
|
187
|
+
extra_args_raw = ["--timeout", "10"]
|
|
188
|
+
|
|
189
|
+
normalized_extra_args = []
|
|
190
|
+
i = 0
|
|
191
|
+
while i < len(extra_args_raw):
|
|
192
|
+
arg = extra_args_raw[i]
|
|
193
|
+
if arg == "--timeout" and i + 1 < len(extra_args_raw):
|
|
194
|
+
val = extra_args_raw[i+1]
|
|
195
|
+
if val.endswith("s"):
|
|
196
|
+
try:
|
|
197
|
+
seconds = float(val[:-1])
|
|
198
|
+
val = str(int(seconds * 1000))
|
|
199
|
+
except ValueError:
|
|
200
|
+
pass
|
|
201
|
+
normalized_extra_args.append(arg)
|
|
202
|
+
normalized_extra_args.append(val)
|
|
203
|
+
i += 2
|
|
204
|
+
elif arg.startswith("--timeout="):
|
|
205
|
+
val = arg.partition("=")[2]
|
|
206
|
+
if val.endswith("s"):
|
|
207
|
+
try:
|
|
208
|
+
seconds = float(val[:-1])
|
|
209
|
+
val = str(int(seconds * 1000))
|
|
210
|
+
except ValueError:
|
|
211
|
+
pass
|
|
212
|
+
normalized_extra_args.append(f"--timeout={val}")
|
|
213
|
+
i += 1
|
|
214
|
+
else:
|
|
215
|
+
normalized_extra_args.append(arg)
|
|
216
|
+
i += 1
|
|
217
|
+
|
|
174
218
|
testql = TestQLConfig(
|
|
175
219
|
scenario_dir=testql_raw.get("scenario_dir", "scenarios/tests"),
|
|
176
220
|
smoke_scenario=testql_raw.get("smoke_scenario", "smoke.testql.toon.yaml"),
|
|
177
221
|
output_format=testql_raw.get("output_format", "json"),
|
|
178
|
-
extra_args=
|
|
222
|
+
extra_args=normalized_extra_args,
|
|
179
223
|
endpoint_discovery=testql_raw.get("endpoint_discovery", True),
|
|
180
224
|
probe_interval_s=int(testql_raw.get("probe_interval_s", 0) or 0),
|
|
181
225
|
health_scenario=testql_raw.get("health_scenario", ""),
|
|
@@ -6,6 +6,7 @@ import asyncio
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
8
|
import re
|
|
9
|
+
import signal
|
|
9
10
|
import subprocess
|
|
10
11
|
import time
|
|
11
12
|
from pathlib import Path
|
|
@@ -380,15 +381,39 @@ class TestQLWatcher(WupWatcher):
|
|
|
380
381
|
text=True,
|
|
381
382
|
timeout=timeout,
|
|
382
383
|
)
|
|
384
|
+
except subprocess.TimeoutExpired:
|
|
385
|
+
return subprocess.CompletedProcess(
|
|
386
|
+
args=cmd,
|
|
387
|
+
returncode=124,
|
|
388
|
+
stdout="",
|
|
389
|
+
stderr=f"Error: TestQL command timed out after {timeout} seconds",
|
|
390
|
+
)
|
|
383
391
|
except FileNotFoundError:
|
|
384
392
|
fallback_cmd = ["python3", "-m", "testql.cli", *args]
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
393
|
+
try:
|
|
394
|
+
return subprocess.run(
|
|
395
|
+
fallback_cmd,
|
|
396
|
+
cwd=str(self.project_root),
|
|
397
|
+
capture_output=True,
|
|
398
|
+
text=True,
|
|
399
|
+
timeout=timeout,
|
|
400
|
+
)
|
|
401
|
+
except subprocess.TimeoutExpired:
|
|
402
|
+
return subprocess.CompletedProcess(
|
|
403
|
+
args=fallback_cmd,
|
|
404
|
+
returncode=124,
|
|
405
|
+
stdout="",
|
|
406
|
+
stderr=f"Error: TestQL command timed out after {timeout} seconds",
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
@staticmethod
|
|
410
|
+
def _is_interrupted_result(result: subprocess.CompletedProcess) -> bool:
|
|
411
|
+
rc = int(result.returncode)
|
|
412
|
+
if rc in {130, 143}:
|
|
413
|
+
return True
|
|
414
|
+
if rc < 0 and (-rc) in {signal.SIGINT, signal.SIGTERM}:
|
|
415
|
+
return True
|
|
416
|
+
return False
|
|
392
417
|
|
|
393
418
|
def _write_track(self, *, service: str, stage: str, scenario: Optional[Path], result: subprocess.CompletedProcess) -> Path:
|
|
394
419
|
ts = int(time.time())
|
|
@@ -450,6 +475,8 @@ class TestQLWatcher(WupWatcher):
|
|
|
450
475
|
result = self._run_testql(args, timeout=self._quick_timeout())
|
|
451
476
|
if result.returncode == 0:
|
|
452
477
|
return True
|
|
478
|
+
if self._is_interrupted_result(result):
|
|
479
|
+
raise KeyboardInterrupt
|
|
453
480
|
|
|
454
481
|
reason = result.stderr.strip() or result.stdout.strip() or "Quick TestQL failed"
|
|
455
482
|
track_path = self._write_track(service=service, stage="quick",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.47
|
|
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.4708 (57 commits)
|
|
38
|
+
- 👤 **Human dev:** ~$2304 (23.0h @ $100/h, 30min dedup)
|
|
39
39
|
|
|
40
|
-
Generated on 2026-05-
|
|
40
|
+
Generated on 2026-05-24 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
|
|
|
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
|
|
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
|