wup 0.2.6__tar.gz → 0.2.7__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.6 → wup-0.2.7}/PKG-INFO +6 -6
- {wup-0.2.6 → wup-0.2.7}/README.md +5 -5
- {wup-0.2.6 → wup-0.2.7}/pyproject.toml +1 -1
- {wup-0.2.6 → wup-0.2.7}/tests/test_wup.py +558 -0
- {wup-0.2.6 → wup-0.2.7}/wup/__init__.py +1 -1
- {wup-0.2.6 → wup-0.2.7}/wup/core.py +16 -8
- {wup-0.2.6 → wup-0.2.7}/wup.egg-info/PKG-INFO +6 -6
- {wup-0.2.6 → wup-0.2.7}/LICENSE +0 -0
- {wup-0.2.6 → wup-0.2.7}/setup.cfg +0 -0
- {wup-0.2.6 → wup-0.2.7}/tests/test_testql_watcher.py +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup/cli.py +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup/config.py +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup/dependency_mapper.py +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup/models/__init__.py +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup/models/config.py +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup/testql_discovery.py +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup/testql_watcher.py +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup.egg-info/SOURCES.txt +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup.egg-info/dependency_links.txt +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup.egg-info/entry_points.txt +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup.egg-info/requires.txt +0 -0
- {wup-0.2.6 → wup-0.2.7}/wup.egg-info/top_level.txt +0 -0
{wup-0.2.6 → wup-0.2.7}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
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
|
|
@@ -28,17 +28,17 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
## AI Cost Tracking
|
|
30
30
|
|
|
31
|
-
    
|
|
32
|
+
  
|
|
33
33
|
|
|
34
|
-
- 🤖 **LLM usage:** $1.
|
|
35
|
-
- 👤 **Human dev:** ~$
|
|
34
|
+
- 🤖 **LLM usage:** $1.2000 (8 commits)
|
|
35
|
+
- 👤 **Human dev:** ~$223 (2.2h @ $100/h, 30min dedup)
|
|
36
36
|
|
|
37
37
|
Generated on 2026-04-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
    
|
|
42
42
|
|
|
43
43
|
**WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
|
|
44
44
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $1.
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
9
|
+
- 🤖 **LLM usage:** $1.2000 (8 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$223 (2.2h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-04-29 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
|
|
|
@@ -103,6 +103,91 @@ def create_user():
|
|
|
103
103
|
|
|
104
104
|
assert mapper2.file_to_endpoints == mapper.file_to_endpoints
|
|
105
105
|
assert mapper2.service_to_endpoints == mapper.service_to_endpoints
|
|
106
|
+
|
|
107
|
+
def test_infer_service_from_path_edge_cases(self):
|
|
108
|
+
"""Test service inference with edge case paths."""
|
|
109
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
110
|
+
mapper = DependencyMapper(tmpdir)
|
|
111
|
+
|
|
112
|
+
# Single directory - returns None (needs at least 2 parts)
|
|
113
|
+
assert mapper._infer_service("app") is None
|
|
114
|
+
|
|
115
|
+
# Very deep nesting
|
|
116
|
+
assert mapper._infer_service("a/b/c/d/e/f/file.py") == "a/b"
|
|
117
|
+
|
|
118
|
+
# Path with numbers
|
|
119
|
+
assert mapper._infer_service("v1/api/routes.py") == "v1/api"
|
|
120
|
+
|
|
121
|
+
# Path with underscores
|
|
122
|
+
assert mapper._infer_service("src/user_auth/login.py") == "src/user_auth"
|
|
123
|
+
|
|
124
|
+
def test_get_service_for_file_empty_mapper(self):
|
|
125
|
+
"""Test getting service for file when mapper is empty."""
|
|
126
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
127
|
+
mapper = DependencyMapper(tmpdir)
|
|
128
|
+
|
|
129
|
+
# Need to use absolute path under tmpdir
|
|
130
|
+
file_path = str(Path(tmpdir) / "app" / "users" / "routes.py")
|
|
131
|
+
service = mapper.get_service_for_file(file_path)
|
|
132
|
+
# Dependency mapper has fallback heuristic that returns first two path parts
|
|
133
|
+
assert service == "app/users"
|
|
134
|
+
|
|
135
|
+
def test_get_endpoints_for_service_empty_mapper(self):
|
|
136
|
+
"""Test getting endpoints for service when mapper is empty."""
|
|
137
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
138
|
+
mapper = DependencyMapper(tmpdir)
|
|
139
|
+
|
|
140
|
+
endpoints = mapper.get_endpoints_for_service("users")
|
|
141
|
+
assert endpoints == []
|
|
142
|
+
|
|
143
|
+
def test_build_from_codebase_with_flask(self):
|
|
144
|
+
"""Test building dependency map with Flask endpoints."""
|
|
145
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
146
|
+
# Create a sample Flask file
|
|
147
|
+
app_dir = Path(tmpdir) / "app" / "auth"
|
|
148
|
+
app_dir.mkdir(parents=True)
|
|
149
|
+
|
|
150
|
+
routes_file = app_dir / "views.py"
|
|
151
|
+
routes_file.write_text("""
|
|
152
|
+
from flask import Blueprint, jsonify
|
|
153
|
+
|
|
154
|
+
bp = Blueprint('auth', __name__)
|
|
155
|
+
|
|
156
|
+
@bp.route('/login', methods=['POST'])
|
|
157
|
+
def login():
|
|
158
|
+
return jsonify({'token': 'abc'})
|
|
159
|
+
|
|
160
|
+
@bp.route('/logout', methods=['POST'])
|
|
161
|
+
def logout():
|
|
162
|
+
return jsonify({'success': True})
|
|
163
|
+
""")
|
|
164
|
+
|
|
165
|
+
mapper = DependencyMapper(tmpdir)
|
|
166
|
+
deps = mapper.build_from_codebase(framework="flask")
|
|
167
|
+
|
|
168
|
+
assert len(deps["services"]) > 0
|
|
169
|
+
assert len(deps["files"]) > 0
|
|
170
|
+
|
|
171
|
+
def test_service_to_files_tracking(self):
|
|
172
|
+
"""Test that service to files mapping is tracked correctly."""
|
|
173
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
174
|
+
mapper = DependencyMapper(tmpdir)
|
|
175
|
+
|
|
176
|
+
# Add file to service
|
|
177
|
+
mapper.service_to_files["users"].add("app/users/routes.py")
|
|
178
|
+
mapper.service_to_files["users"].add("app/users/models.py")
|
|
179
|
+
|
|
180
|
+
assert len(mapper.service_to_files["users"]) == 2
|
|
181
|
+
assert "app/users/routes.py" in mapper.service_to_files["users"]
|
|
182
|
+
|
|
183
|
+
def test_build_from_codebase_nonexistent_directory(self):
|
|
184
|
+
"""Test building from codebase with non-existent directory."""
|
|
185
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
186
|
+
mapper = DependencyMapper(tmpdir)
|
|
187
|
+
# Should not fail, just return empty deps
|
|
188
|
+
deps = mapper.build_from_codebase(framework="fastapi")
|
|
189
|
+
assert "services" in deps
|
|
190
|
+
assert "files" in deps
|
|
106
191
|
|
|
107
192
|
|
|
108
193
|
class TestWupWatcher:
|
|
@@ -152,6 +237,102 @@ class TestWupWatcher:
|
|
|
152
237
|
service = watcher.infer_service(str(Path(tmpdir) / "app" / "users" / "routes.py"))
|
|
153
238
|
assert service == "app/users"
|
|
154
239
|
|
|
240
|
+
def test_infer_service_with_auto_detection(self):
|
|
241
|
+
"""Test service inference with auto-detection from config."""
|
|
242
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
243
|
+
# Create service with auto-detection (empty paths)
|
|
244
|
+
service = ServiceConfig(
|
|
245
|
+
name="users-shell",
|
|
246
|
+
root="app/users-shell",
|
|
247
|
+
paths=[], # Empty paths triggers auto-detection
|
|
248
|
+
type="shell"
|
|
249
|
+
)
|
|
250
|
+
config = WupConfig(
|
|
251
|
+
project=ProjectConfig(name="test"),
|
|
252
|
+
watch=WatchConfig(),
|
|
253
|
+
services=[service],
|
|
254
|
+
test_strategy=TestStrategyConfig(),
|
|
255
|
+
testql=TestQLConfig()
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
259
|
+
|
|
260
|
+
# File containing "users-shell" should match
|
|
261
|
+
inferred = watcher.infer_service(str(Path(tmpdir) / "app" / "users-shell" / "main.py"))
|
|
262
|
+
assert inferred == "users-shell"
|
|
263
|
+
|
|
264
|
+
# File containing "users" should not match auto-detection
|
|
265
|
+
# Fallback heuristic returns "app/users" from dependency mapper
|
|
266
|
+
inferred = watcher.infer_service(str(Path(tmpdir) / "app" / "users" / "main.py"))
|
|
267
|
+
assert inferred == "app/users" # Fallback heuristic
|
|
268
|
+
|
|
269
|
+
def test_infer_service_with_explicit_paths(self):
|
|
270
|
+
"""Test service inference with explicit config paths."""
|
|
271
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
272
|
+
service = ServiceConfig(
|
|
273
|
+
name="users",
|
|
274
|
+
root="app/users",
|
|
275
|
+
paths=["app/users/**", "routes/users/**"],
|
|
276
|
+
type="auto"
|
|
277
|
+
)
|
|
278
|
+
config = WupConfig(
|
|
279
|
+
project=ProjectConfig(name="test"),
|
|
280
|
+
watch=WatchConfig(),
|
|
281
|
+
services=[service],
|
|
282
|
+
test_strategy=TestStrategyConfig(),
|
|
283
|
+
testql=TestQLConfig()
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
287
|
+
|
|
288
|
+
# Explicit path should match
|
|
289
|
+
inferred = watcher.infer_service(str(Path(tmpdir) / "app" / "users" / "routes.py"))
|
|
290
|
+
assert inferred == "users"
|
|
291
|
+
|
|
292
|
+
# Alternative explicit path should match
|
|
293
|
+
inferred = watcher.infer_service(str(Path(tmpdir) / "routes" / "users" / "main.py"))
|
|
294
|
+
assert inferred == "users"
|
|
295
|
+
|
|
296
|
+
def test_infer_service_priority_config_over_mapper(self):
|
|
297
|
+
"""Test that config services take priority over dependency mapper."""
|
|
298
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
299
|
+
service = ServiceConfig(
|
|
300
|
+
name="custom-service",
|
|
301
|
+
root="app/custom",
|
|
302
|
+
paths=["app/custom/**"],
|
|
303
|
+
type="auto"
|
|
304
|
+
)
|
|
305
|
+
config = WupConfig(
|
|
306
|
+
project=ProjectConfig(name="test"),
|
|
307
|
+
watch=WatchConfig(),
|
|
308
|
+
services=[service],
|
|
309
|
+
test_strategy=TestStrategyConfig(),
|
|
310
|
+
testql=TestQLConfig()
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
314
|
+
|
|
315
|
+
# Config should take priority
|
|
316
|
+
inferred = watcher.infer_service(str(Path(tmpdir) / "app" / "custom" / "file.py"))
|
|
317
|
+
assert inferred == "custom-service"
|
|
318
|
+
|
|
319
|
+
def test_infer_service_fallback_to_heuristics(self):
|
|
320
|
+
"""Test fallback to heuristics when no config or mapper match."""
|
|
321
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
322
|
+
config = WupConfig(
|
|
323
|
+
project=ProjectConfig(name="test"),
|
|
324
|
+
watch=WatchConfig(),
|
|
325
|
+
services=[],
|
|
326
|
+
test_strategy=TestStrategyConfig(),
|
|
327
|
+
testql=TestQLConfig()
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
331
|
+
|
|
332
|
+
# Should fallback to heuristics (first two path parts)
|
|
333
|
+
inferred = watcher.infer_service(str(Path(tmpdir) / "app" / "users" / "routes.py"))
|
|
334
|
+
assert inferred == "app/users"
|
|
335
|
+
|
|
155
336
|
def test_should_test_cooldown(self):
|
|
156
337
|
"""Test cooldown mechanism for testing."""
|
|
157
338
|
import time
|
|
@@ -224,6 +405,383 @@ class TestWupWatcher:
|
|
|
224
405
|
|
|
225
406
|
# No services should have been added
|
|
226
407
|
assert len(watcher.changed_services) == 0
|
|
408
|
+
|
|
409
|
+
def test_detect_service_coincidences_shell_web(self):
|
|
410
|
+
"""Test coincidence detection between shell and web services."""
|
|
411
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
412
|
+
service1 = ServiceConfig(
|
|
413
|
+
name="users-shell",
|
|
414
|
+
root="app/users-shell",
|
|
415
|
+
type="shell",
|
|
416
|
+
paths=[]
|
|
417
|
+
)
|
|
418
|
+
service2 = ServiceConfig(
|
|
419
|
+
name="users-web",
|
|
420
|
+
root="app/users-web",
|
|
421
|
+
type="web",
|
|
422
|
+
paths=[]
|
|
423
|
+
)
|
|
424
|
+
service3 = ServiceConfig(
|
|
425
|
+
name="payments-shell",
|
|
426
|
+
root="app/payments-shell",
|
|
427
|
+
type="shell",
|
|
428
|
+
paths=[]
|
|
429
|
+
)
|
|
430
|
+
config = WupConfig(
|
|
431
|
+
project=ProjectConfig(name="test"),
|
|
432
|
+
watch=WatchConfig(),
|
|
433
|
+
services=[service1, service2, service3],
|
|
434
|
+
test_strategy=TestStrategyConfig(),
|
|
435
|
+
testql=TestQLConfig()
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
439
|
+
|
|
440
|
+
# users-shell should detect users-web as related
|
|
441
|
+
related = watcher.detect_service_coincidences("users-shell")
|
|
442
|
+
assert "users-web" in related
|
|
443
|
+
assert "payments-shell" not in related
|
|
444
|
+
|
|
445
|
+
# users-web should detect users-shell as related
|
|
446
|
+
related = watcher.detect_service_coincidences("users-web")
|
|
447
|
+
assert "users-shell" in related
|
|
448
|
+
assert "payments-shell" not in related
|
|
449
|
+
|
|
450
|
+
def test_detect_service_coincidences_auto_type(self):
|
|
451
|
+
"""Test coincidence detection with auto type services."""
|
|
452
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
453
|
+
service1 = ServiceConfig(
|
|
454
|
+
name="users",
|
|
455
|
+
root="app/users",
|
|
456
|
+
type="auto",
|
|
457
|
+
paths=[]
|
|
458
|
+
)
|
|
459
|
+
service2 = ServiceConfig(
|
|
460
|
+
name="users-api",
|
|
461
|
+
root="app/users-api",
|
|
462
|
+
type="auto",
|
|
463
|
+
paths=[]
|
|
464
|
+
)
|
|
465
|
+
config = WupConfig(
|
|
466
|
+
project=ProjectConfig(name="test"),
|
|
467
|
+
watch=WatchConfig(),
|
|
468
|
+
services=[service1, service2],
|
|
469
|
+
test_strategy=TestStrategyConfig(),
|
|
470
|
+
testql=TestQLConfig()
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
474
|
+
|
|
475
|
+
# users and users-api don't share domain (only shell/web suffixes are handled)
|
|
476
|
+
# Since both are auto type and don't have shell/web suffixes, they don't match
|
|
477
|
+
related = watcher.detect_service_coincidences("users")
|
|
478
|
+
assert "users-api" not in related
|
|
479
|
+
|
|
480
|
+
def test_detect_service_coincidences_no_config(self):
|
|
481
|
+
"""Test coincidence detection with no configured services."""
|
|
482
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
483
|
+
config = WupConfig(
|
|
484
|
+
project=ProjectConfig(name="test"),
|
|
485
|
+
watch=WatchConfig(),
|
|
486
|
+
services=[],
|
|
487
|
+
test_strategy=TestStrategyConfig(),
|
|
488
|
+
testql=TestQLConfig()
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
492
|
+
|
|
493
|
+
related = watcher.detect_service_coincidences("users")
|
|
494
|
+
assert len(related) == 0
|
|
495
|
+
|
|
496
|
+
def test_detect_service_coincidences_unknown_service(self):
|
|
497
|
+
"""Test coincidence detection for unknown service."""
|
|
498
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
499
|
+
service1 = ServiceConfig(
|
|
500
|
+
name="users-shell",
|
|
501
|
+
root="app/users-shell",
|
|
502
|
+
type="shell",
|
|
503
|
+
paths=[]
|
|
504
|
+
)
|
|
505
|
+
config = WupConfig(
|
|
506
|
+
project=ProjectConfig(name="test"),
|
|
507
|
+
watch=WatchConfig(),
|
|
508
|
+
services=[service1],
|
|
509
|
+
test_strategy=TestStrategyConfig(),
|
|
510
|
+
testql=TestQLConfig()
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
514
|
+
|
|
515
|
+
# Unknown service should return empty list
|
|
516
|
+
related = watcher.detect_service_coincidences("unknown")
|
|
517
|
+
assert len(related) == 0
|
|
518
|
+
|
|
519
|
+
def test_services_share_domain(self):
|
|
520
|
+
"""Test the _services_share_domain helper method."""
|
|
521
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
522
|
+
watcher = WupWatcher(tmpdir)
|
|
523
|
+
|
|
524
|
+
# Same domain with different suffixes
|
|
525
|
+
assert watcher._services_share_domain("users-shell", "users-web")
|
|
526
|
+
assert watcher._services_share_domain("users-shell", "users")
|
|
527
|
+
assert watcher._services_share_domain("payments", "payments-shell")
|
|
528
|
+
|
|
529
|
+
# Different domains
|
|
530
|
+
assert not watcher._services_share_domain("users", "payments")
|
|
531
|
+
assert not watcher._services_share_domain("api/auth", "api/users")
|
|
532
|
+
|
|
533
|
+
# Underscore variants
|
|
534
|
+
assert watcher._services_share_domain("users_shell", "users_web")
|
|
535
|
+
assert watcher._services_share_domain("users_shell", "users")
|
|
536
|
+
|
|
537
|
+
def test_on_file_change_filters_by_file_type(self):
|
|
538
|
+
"""Test that file change respects configured file types."""
|
|
539
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
540
|
+
(Path(tmpdir) / "app").mkdir()
|
|
541
|
+
|
|
542
|
+
watch = WatchConfig(
|
|
543
|
+
paths=["app/**"],
|
|
544
|
+
file_types=[".py", ".ts"]
|
|
545
|
+
)
|
|
546
|
+
config = WupConfig(
|
|
547
|
+
project=ProjectConfig(name="test"),
|
|
548
|
+
watch=watch,
|
|
549
|
+
services=[],
|
|
550
|
+
test_strategy=TestStrategyConfig(),
|
|
551
|
+
testql=TestQLConfig()
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
555
|
+
|
|
556
|
+
# Python file should be processed
|
|
557
|
+
py_file = str(Path(tmpdir) / "app" / "test.py")
|
|
558
|
+
watcher.on_file_change(py_file)
|
|
559
|
+
|
|
560
|
+
# TypeScript file should be processed
|
|
561
|
+
ts_file = str(Path(tmpdir) / "app" / "test.ts")
|
|
562
|
+
watcher.on_file_change(ts_file)
|
|
563
|
+
|
|
564
|
+
# Markdown file should be filtered out
|
|
565
|
+
md_file = str(Path(tmpdir) / "app" / "test.md")
|
|
566
|
+
watcher.on_file_change(md_file)
|
|
567
|
+
|
|
568
|
+
# Text file should be filtered out
|
|
569
|
+
txt_file = str(Path(tmpdir) / "app" / "test.txt")
|
|
570
|
+
watcher.on_file_change(txt_file)
|
|
571
|
+
|
|
572
|
+
# Only .py and .ts files should trigger service detection
|
|
573
|
+
# (though no services are configured, so changed_services will be empty)
|
|
574
|
+
# The key is that no errors occur and filtering works
|
|
575
|
+
|
|
576
|
+
def test_on_file_change_no_file_type_filter(self):
|
|
577
|
+
"""Test that when file_types is empty, all files are processed."""
|
|
578
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
579
|
+
(Path(tmpdir) / "app").mkdir()
|
|
580
|
+
|
|
581
|
+
watch = WatchConfig(
|
|
582
|
+
paths=["app/**"],
|
|
583
|
+
file_types=[]
|
|
584
|
+
)
|
|
585
|
+
config = WupConfig(
|
|
586
|
+
project=ProjectConfig(name="test"),
|
|
587
|
+
watch=watch,
|
|
588
|
+
services=[],
|
|
589
|
+
test_strategy=TestStrategyConfig(),
|
|
590
|
+
testql=TestQLConfig()
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
594
|
+
|
|
595
|
+
# All file types should be processed
|
|
596
|
+
py_file = str(Path(tmpdir) / "app" / "test.py")
|
|
597
|
+
watcher.on_file_change(py_file)
|
|
598
|
+
|
|
599
|
+
md_file = str(Path(tmpdir) / "app" / "test.md")
|
|
600
|
+
watcher.on_file_change(md_file)
|
|
601
|
+
|
|
602
|
+
# No filtering should occur
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
class TestIntegrationWorkflow:
|
|
606
|
+
"""Integration tests for complete workflows."""
|
|
607
|
+
|
|
608
|
+
def test_full_workflow_file_change_to_test_scheduling(self):
|
|
609
|
+
"""Test complete workflow from file change to test scheduling."""
|
|
610
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
611
|
+
(Path(tmpdir) / "app").mkdir()
|
|
612
|
+
|
|
613
|
+
service = ServiceConfig(
|
|
614
|
+
name="users",
|
|
615
|
+
root="app/users",
|
|
616
|
+
paths=["app/users/**"],
|
|
617
|
+
quick_tests=ServiceTestConfig(scope="all", max_endpoints=3)
|
|
618
|
+
)
|
|
619
|
+
config = WupConfig(
|
|
620
|
+
project=ProjectConfig(name="test"),
|
|
621
|
+
watch=WatchConfig(paths=["app/**"]),
|
|
622
|
+
services=[service],
|
|
623
|
+
test_strategy=TestStrategyConfig(),
|
|
624
|
+
testql=TestQLConfig()
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
628
|
+
watcher.dependency_mapper.service_to_endpoints["users"] = [
|
|
629
|
+
"/api/users",
|
|
630
|
+
"/api/users/{id}",
|
|
631
|
+
"/api/users/create"
|
|
632
|
+
]
|
|
633
|
+
|
|
634
|
+
# Simulate file change
|
|
635
|
+
file_path = str(Path(tmpdir) / "app" / "users" / "routes.py")
|
|
636
|
+
watcher.on_file_change(file_path)
|
|
637
|
+
|
|
638
|
+
# Verify service was detected
|
|
639
|
+
assert "users" in watcher.changed_services
|
|
640
|
+
|
|
641
|
+
# Verify test was scheduled
|
|
642
|
+
assert len(watcher.test_queue) == 1
|
|
643
|
+
test_type, service_name, endpoints = watcher.test_queue[0]
|
|
644
|
+
assert test_type == "quick"
|
|
645
|
+
assert service_name == "users"
|
|
646
|
+
assert len(endpoints) == 3 # Limited by quick_tests.max_endpoints
|
|
647
|
+
|
|
648
|
+
def test_workflow_with_file_type_filtering(self):
|
|
649
|
+
"""Test workflow with file type filtering."""
|
|
650
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
651
|
+
(Path(tmpdir) / "app").mkdir()
|
|
652
|
+
|
|
653
|
+
watch = WatchConfig(
|
|
654
|
+
paths=["app/**"],
|
|
655
|
+
file_types=[".py"]
|
|
656
|
+
)
|
|
657
|
+
service = ServiceConfig(
|
|
658
|
+
name="users",
|
|
659
|
+
root="app/users",
|
|
660
|
+
paths=["app/users/**"]
|
|
661
|
+
)
|
|
662
|
+
config = WupConfig(
|
|
663
|
+
project=ProjectConfig(name="test"),
|
|
664
|
+
watch=watch,
|
|
665
|
+
services=[service],
|
|
666
|
+
test_strategy=TestStrategyConfig(),
|
|
667
|
+
testql=TestQLConfig()
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
671
|
+
watcher.dependency_mapper.service_to_endpoints["users"] = ["/api/users"]
|
|
672
|
+
|
|
673
|
+
# Python file should trigger
|
|
674
|
+
py_file = str(Path(tmpdir) / "app" / "users" / "routes.py")
|
|
675
|
+
watcher.on_file_change(py_file)
|
|
676
|
+
assert "users" in watcher.changed_services
|
|
677
|
+
|
|
678
|
+
# Markdown file should not trigger
|
|
679
|
+
watcher.changed_services.clear()
|
|
680
|
+
md_file = str(Path(tmpdir) / "app" / "users" / "README.md")
|
|
681
|
+
watcher.on_file_change(md_file)
|
|
682
|
+
assert "users" not in watcher.changed_services
|
|
683
|
+
|
|
684
|
+
def test_workflow_with_service_coincidence(self):
|
|
685
|
+
"""Test workflow that detects service coincidences."""
|
|
686
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
687
|
+
service1 = ServiceConfig(
|
|
688
|
+
name="users-shell",
|
|
689
|
+
root="app/users-shell",
|
|
690
|
+
type="shell",
|
|
691
|
+
paths=[]
|
|
692
|
+
)
|
|
693
|
+
service2 = ServiceConfig(
|
|
694
|
+
name="users-web",
|
|
695
|
+
root="app/users-web",
|
|
696
|
+
type="web",
|
|
697
|
+
paths=[]
|
|
698
|
+
)
|
|
699
|
+
config = WupConfig(
|
|
700
|
+
project=ProjectConfig(name="test"),
|
|
701
|
+
watch=WatchConfig(),
|
|
702
|
+
services=[service1, service2],
|
|
703
|
+
test_strategy=TestStrategyConfig(),
|
|
704
|
+
testql=TestQLConfig()
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
708
|
+
|
|
709
|
+
# Detect coincidences
|
|
710
|
+
related = watcher.detect_service_coincidences("users-shell")
|
|
711
|
+
assert "users-web" in related
|
|
712
|
+
|
|
713
|
+
def test_workflow_with_multiple_file_changes(self):
|
|
714
|
+
"""Test workflow with multiple rapid file changes."""
|
|
715
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
716
|
+
(Path(tmpdir) / "app").mkdir()
|
|
717
|
+
|
|
718
|
+
service = ServiceConfig(
|
|
719
|
+
name="users",
|
|
720
|
+
root="app/users",
|
|
721
|
+
paths=["app/users/**"]
|
|
722
|
+
)
|
|
723
|
+
config = WupConfig(
|
|
724
|
+
project=ProjectConfig(name="test"),
|
|
725
|
+
watch=WatchConfig(paths=["app/**"]),
|
|
726
|
+
services=[service],
|
|
727
|
+
test_strategy=TestStrategyConfig(),
|
|
728
|
+
testql=TestQLConfig()
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
732
|
+
watcher.dependency_mapper.service_to_endpoints["users"] = ["/api/users"]
|
|
733
|
+
|
|
734
|
+
# Multiple file changes for same service
|
|
735
|
+
files = [
|
|
736
|
+
str(Path(tmpdir) / "app" / "users" / "routes.py"),
|
|
737
|
+
str(Path(tmpdir) / "app" / "users" / "models.py"),
|
|
738
|
+
str(Path(tmpdir) / "app" / "users" / "schemas.py"),
|
|
739
|
+
]
|
|
740
|
+
|
|
741
|
+
for file_path in files:
|
|
742
|
+
watcher.on_file_change(file_path)
|
|
743
|
+
|
|
744
|
+
# Service should be in changed_services
|
|
745
|
+
assert "users" in watcher.changed_services
|
|
746
|
+
|
|
747
|
+
# Multiple tests might be scheduled depending on debounce
|
|
748
|
+
# But service should be tracked
|
|
749
|
+
assert len(watcher.changed_services) == 1
|
|
750
|
+
|
|
751
|
+
def test_workflow_with_auto_detection_and_explicit_paths(self):
|
|
752
|
+
"""Test workflow mixing auto-detection and explicit paths."""
|
|
753
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
754
|
+
(Path(tmpdir) / "app").mkdir()
|
|
755
|
+
|
|
756
|
+
service1 = ServiceConfig(
|
|
757
|
+
name="users-shell",
|
|
758
|
+
root="app/users-shell",
|
|
759
|
+
type="shell",
|
|
760
|
+
paths=[] # Auto-detection
|
|
761
|
+
)
|
|
762
|
+
service2 = ServiceConfig(
|
|
763
|
+
name="payments",
|
|
764
|
+
root="app/payments",
|
|
765
|
+
type="auto",
|
|
766
|
+
paths=["app/payments/**"] # Explicit paths
|
|
767
|
+
)
|
|
768
|
+
config = WupConfig(
|
|
769
|
+
project=ProjectConfig(name="test"),
|
|
770
|
+
watch=WatchConfig(),
|
|
771
|
+
services=[service1, service2],
|
|
772
|
+
test_strategy=TestStrategyConfig(),
|
|
773
|
+
testql=TestQLConfig()
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
watcher = WupWatcher(tmpdir, config=config)
|
|
777
|
+
|
|
778
|
+
# Auto-detection should match
|
|
779
|
+
inferred1 = watcher.infer_service(str(Path(tmpdir) / "app" / "users-shell" / "main.py"))
|
|
780
|
+
assert inferred1 == "users-shell"
|
|
781
|
+
|
|
782
|
+
# Explicit path should match
|
|
783
|
+
inferred2 = watcher.infer_service(str(Path(tmpdir) / "app" / "payments" / "routes.py"))
|
|
784
|
+
assert inferred2 == "payments"
|
|
227
785
|
|
|
228
786
|
|
|
229
787
|
def test_import():
|
|
@@ -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.7"
|
|
11
11
|
__author__ = "Tom Sapletta"
|
|
12
12
|
|
|
13
13
|
from .config import load_config, save_config, get_default_config
|
|
@@ -122,19 +122,26 @@ class WupWatcher:
|
|
|
122
122
|
return svc.name
|
|
123
123
|
else:
|
|
124
124
|
# Auto-detect: check if service name appears in path
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
# Require the full service name to match as a complete segment
|
|
126
|
+
path_lower = str(rel_path).lower()
|
|
127
|
+
service_name_lower = svc.name.lower()
|
|
128
|
+
|
|
129
|
+
# Check if service name appears as a complete segment (separated by /, -, _, or .)
|
|
130
|
+
import re
|
|
131
|
+
pattern = r'(?:[\/\-_.]|^)' + re.escape(service_name_lower) + r'(?:[\/\-_.]|$)'
|
|
132
|
+
if re.search(pattern, path_lower):
|
|
133
|
+
return svc.name
|
|
129
134
|
|
|
130
135
|
# Use dependency mapper if available
|
|
131
136
|
service = self.dependency_mapper.get_service_for_file(file_path)
|
|
132
137
|
if service:
|
|
133
138
|
return service
|
|
134
139
|
|
|
135
|
-
# Fallback: use first two meaningful parts
|
|
140
|
+
# Fallback: use first two meaningful parts (only if file exists)
|
|
136
141
|
if len(parts) >= 2:
|
|
137
|
-
|
|
142
|
+
# Check if file exists (absolute path)
|
|
143
|
+
if Path(file_path).is_file():
|
|
144
|
+
return "/".join(parts[:2])
|
|
138
145
|
|
|
139
146
|
return None
|
|
140
147
|
|
|
@@ -179,8 +186,9 @@ class WupWatcher:
|
|
|
179
186
|
if self._services_share_domain(changed_service, svc.name):
|
|
180
187
|
related_services.append(svc.name)
|
|
181
188
|
|
|
182
|
-
# Coincidence: auto
|
|
183
|
-
elif changed_svc_config.type == "auto"
|
|
189
|
+
# Coincidence: auto with shell/web (but not auto with auto)
|
|
190
|
+
elif (changed_svc_config.type == "auto" and svc.type != "auto") or \
|
|
191
|
+
(changed_svc_config.type != "auto" and svc.type == "auto"):
|
|
184
192
|
if self._services_share_domain(changed_service, svc.name):
|
|
185
193
|
related_services.append(svc.name)
|
|
186
194
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
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
|
|
@@ -28,17 +28,17 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
## AI Cost Tracking
|
|
30
30
|
|
|
31
|
-
    
|
|
32
|
+
  
|
|
33
33
|
|
|
34
|
-
- 🤖 **LLM usage:** $1.
|
|
35
|
-
- 👤 **Human dev:** ~$
|
|
34
|
+
- 🤖 **LLM usage:** $1.2000 (8 commits)
|
|
35
|
+
- 👤 **Human dev:** ~$223 (2.2h @ $100/h, 30min dedup)
|
|
36
36
|
|
|
37
37
|
Generated on 2026-04-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
    
|
|
42
42
|
|
|
43
43
|
**WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
|
|
44
44
|
|
{wup-0.2.6 → wup-0.2.7}/LICENSE
RENAMED
|
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
|