wup 0.2.16__tar.gz → 0.2.21__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.16/wup.egg-info → wup-0.2.21}/PKG-INFO +107 -22
- {wup-0.2.16 → wup-0.2.21}/README.md +106 -21
- {wup-0.2.16 → wup-0.2.21}/pyproject.toml +1 -1
- {wup-0.2.16 → wup-0.2.21}/wup/__init__.py +1 -1
- wup-0.2.21/wup/_ast_detector.py +124 -0
- wup-0.2.21/wup/_hash_detector.py +72 -0
- wup-0.2.21/wup/_yaml_detector.py +128 -0
- wup-0.2.21/wup/anomaly_detector.py +175 -0
- wup-0.2.21/wup/anomaly_models.py +35 -0
- wup-0.2.21/wup/assistant.py +694 -0
- {wup-0.2.16 → wup-0.2.21}/wup/cli.py +28 -0
- {wup-0.2.16 → wup-0.2.21}/wup/config.py +60 -5
- {wup-0.2.16 → wup-0.2.21}/wup/core.py +20 -34
- {wup-0.2.16 → wup-0.2.21}/wup/models/__init__.py +21 -8
- {wup-0.2.16 → wup-0.2.21}/wup/models/config.py +29 -3
- {wup-0.2.16 → wup-0.2.21}/wup/testql_watcher.py +115 -80
- {wup-0.2.16 → wup-0.2.21}/wup/visual_diff.py +64 -9
- {wup-0.2.16 → wup-0.2.21}/wup/web_client.py +4 -4
- {wup-0.2.16 → wup-0.2.21/wup.egg-info}/PKG-INFO +107 -22
- {wup-0.2.16 → wup-0.2.21}/wup.egg-info/SOURCES.txt +6 -14
- wup-0.2.21/wup.egg-info/top_level.txt +1 -0
- wup-0.2.16/wup-web/tests/__init__.py +0 -0
- wup-0.2.16/wup-web/tests/conftest.py +0 -23
- wup-0.2.16/wup-web/tests/test_dashboard.py +0 -35
- wup-0.2.16/wup-web/tests/test_drivers.py +0 -50
- wup-0.2.16/wup-web/tests/test_events.py +0 -95
- wup-0.2.16/wup-web/wup_web/__init__.py +0 -7
- wup-0.2.16/wup-web/wup_web/__main__.py +0 -21
- wup-0.2.16/wup-web/wup_web/main.py +0 -44
- wup-0.2.16/wup-web/wup_web/models.py +0 -59
- wup-0.2.16/wup-web/wup_web/routers/__init__.py +0 -1
- wup-0.2.16/wup-web/wup_web/routers/dashboard.py +0 -24
- wup-0.2.16/wup-web/wup_web/routers/drivers.py +0 -129
- wup-0.2.16/wup-web/wup_web/routers/events.py +0 -48
- wup-0.2.16/wup-web/wup_web/storage.py +0 -110
- wup-0.2.16/wup.egg-info/top_level.txt +0 -2
- {wup-0.2.16 → wup-0.2.21}/LICENSE +0 -0
- {wup-0.2.16 → wup-0.2.21}/setup.cfg +0 -0
- {wup-0.2.16 → wup-0.2.21}/tests/test_e2e.py +0 -0
- {wup-0.2.16 → wup-0.2.21}/tests/test_testql_watcher.py +0 -0
- {wup-0.2.16 → wup-0.2.21}/tests/test_web_client.py +0 -0
- {wup-0.2.16 → wup-0.2.21}/tests/test_wup.py +0 -0
- {wup-0.2.16 → wup-0.2.21}/wup/dependency_mapper.py +0 -0
- {wup-0.2.16 → wup-0.2.21}/wup/testql_discovery.py +0 -0
- {wup-0.2.16 → wup-0.2.21}/wup.egg-info/dependency_links.txt +0 -0
- {wup-0.2.16 → wup-0.2.21}/wup.egg-info/entry_points.txt +0 -0
- {wup-0.2.16 → wup-0.2.21}/wup.egg-info/requires.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.21
|
|
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: Apache-2.0
|
|
@@ -29,17 +29,17 @@ Dynamic: license-file
|
|
|
29
29
|
|
|
30
30
|
## AI Cost Tracking
|
|
31
31
|
|
|
32
|
-
    
|
|
33
|
+
  
|
|
34
34
|
|
|
35
|
-
- 🤖 **LLM usage:** $
|
|
36
|
-
- 👤 **Human dev:** ~$
|
|
35
|
+
- 🤖 **LLM usage:** $4.8000 (32 commits)
|
|
36
|
+
- 👤 **Human dev:** ~$913 (9.1h @ $100/h, 30min dedup)
|
|
37
37
|
|
|
38
|
-
Generated on 2026-
|
|
38
|
+
Generated on 2026-05-01 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
-
    
|
|
43
43
|
|
|
44
44
|
**WUP (What's Up)** - Intelligent file watcher for regression testing in large projects.
|
|
45
45
|
|
|
@@ -74,16 +74,19 @@ pip install -e ".[dev]"
|
|
|
74
74
|
## Quick Start
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
# 1.
|
|
78
|
-
wup
|
|
77
|
+
# 1. Interactive configuration (recommended)
|
|
78
|
+
wup assistant
|
|
79
|
+
|
|
80
|
+
# 2. Or quick auto-setup
|
|
81
|
+
wup assistant --quick --template fastapi
|
|
79
82
|
|
|
80
|
-
#
|
|
83
|
+
# 3. Build dependency map (one-time setup)
|
|
81
84
|
wup map-deps ./my-project
|
|
82
85
|
|
|
83
|
-
#
|
|
86
|
+
# 4. Start watching for changes
|
|
84
87
|
wup watch ./my-project
|
|
85
88
|
|
|
86
|
-
#
|
|
89
|
+
# 5. Start with live dashboard
|
|
87
90
|
wup watch ./my-project --dashboard
|
|
88
91
|
```
|
|
89
92
|
|
|
@@ -186,6 +189,16 @@ Full regression: 15s test → 15% CPU spike
|
|
|
186
189
|
|
|
187
190
|
## Configuration
|
|
188
191
|
|
|
192
|
+
### Service Types
|
|
193
|
+
|
|
194
|
+
WUP supports three service types for coincidence detection and intelligent testing:
|
|
195
|
+
|
|
196
|
+
- **web** - HTTP/API services (FastAPI, Flask, Django, Express.js, etc.)
|
|
197
|
+
- **shell** - CLI tools, scripts, and command-line services
|
|
198
|
+
- **auto** - Automatic detection (default)
|
|
199
|
+
|
|
200
|
+
Coincidence detection allows WUP to identify related services. For example, if you have `users-web` and `users-shell`, WUP will detect they share the same domain and test both when relevant files change.
|
|
201
|
+
|
|
189
202
|
### wup.yaml Configuration File
|
|
190
203
|
|
|
191
204
|
WUP supports declarative configuration via `wup.yaml` (or `.wup.yaml`) in your project root. This allows you to define watch paths, service-specific settings, and test strategies.
|
|
@@ -196,6 +209,11 @@ Generate a default configuration:
|
|
|
196
209
|
wup init
|
|
197
210
|
```
|
|
198
211
|
|
|
212
|
+
The generated `wup.yaml` includes:
|
|
213
|
+
- **Metadata header**: Version, generation date, documentation links
|
|
214
|
+
- **Dependencies info**: WUP version and optional wupbro dashboard
|
|
215
|
+
- **Quick start guide**: Common commands to get started
|
|
216
|
+
|
|
199
217
|
Example `wup.yaml`:
|
|
200
218
|
|
|
201
219
|
```yaml
|
|
@@ -285,6 +303,26 @@ export WUP_CPU_THROTTLE=0.5
|
|
|
285
303
|
export WUP_DEBOUNCE=3
|
|
286
304
|
```
|
|
287
305
|
|
|
306
|
+
Full list of supported variables (see `.env.example`):
|
|
307
|
+
|
|
308
|
+
| Variable | Default | Description |
|
|
309
|
+
|----------|---------|-------------|
|
|
310
|
+
| `WUP_CPU_THROTTLE` | — | CPU usage threshold (0.0-1.0) |
|
|
311
|
+
| `WUP_DEBOUNCE` | — | Debounce time in seconds |
|
|
312
|
+
| `WUPBRO_ENDPOINT` | — | wupbro backend URL |
|
|
313
|
+
| `WUP_BASE_URL` | — | Base URL for visual diff page scanning |
|
|
314
|
+
| `OPENROUTER_API_KEY` | *(not set)* | Required for LLM features (https://openrouter.ai/keys) |
|
|
315
|
+
| `LLM_MODEL` | `openrouter/qwen/qwen3-coder-next` | LLM model for assistant |
|
|
316
|
+
| `PFIX_AUTO_APPLY` | `true` | Apply fixes without asking |
|
|
317
|
+
| `PFIX_AUTO_INSTALL_DEPS` | `true` | Auto pip/uv install missing deps |
|
|
318
|
+
| `PFIX_AUTO_RESTART` | `false` | Restart process after fix |
|
|
319
|
+
| `PFIX_MAX_RETRIES` | `3` | Max fix retries |
|
|
320
|
+
| `PFIX_DRY_RUN` | `false` | Dry-run mode (no changes written) |
|
|
321
|
+
| `PFIX_ENABLED` | `true` | Enable/disable pfix |
|
|
322
|
+
| `PFIX_GIT_COMMIT` | `false` | Auto-commit fixes |
|
|
323
|
+
| `PFIX_GIT_PREFIX` | `pfix:` | Commit message prefix |
|
|
324
|
+
| `PFIX_CREATE_BACKUPS` | `false` | Create .pfix_backups/ directory |
|
|
325
|
+
|
|
288
326
|
## Visual DOM Diff
|
|
289
327
|
|
|
290
328
|
WUP optionally scans configured pages with Playwright after each successful quick test, compares the DOM structure to the previous snapshot, and reports significant changes.
|
|
@@ -333,15 +371,15 @@ Visible in `wup status` as a "Visual DOM diffs" section.
|
|
|
333
371
|
|
|
334
372
|
If Playwright is not installed, the visual diff module logs a warning and skips scanning — it does **not** break the watcher.
|
|
335
373
|
|
|
336
|
-
## Web Dashboard (
|
|
374
|
+
## Web Dashboard (wupbro)
|
|
337
375
|
|
|
338
376
|
Optional FastAPI backend that receives events from WUP agents and renders a live dashboard.
|
|
339
377
|
|
|
340
378
|
### Run
|
|
341
379
|
|
|
342
380
|
```bash
|
|
343
|
-
pip install -e
|
|
344
|
-
|
|
381
|
+
pip install -e wupbro/
|
|
382
|
+
wupbro --reload --port 8000
|
|
345
383
|
```
|
|
346
384
|
|
|
347
385
|
Open <http://localhost:8000/> to see regressions, passes, anomalies, visual diffs, and health transitions in real time.
|
|
@@ -359,12 +397,12 @@ web:
|
|
|
359
397
|
Or via env:
|
|
360
398
|
|
|
361
399
|
```bash
|
|
362
|
-
export
|
|
400
|
+
export WUPBRO_ENDPOINT=http://localhost:8000
|
|
363
401
|
```
|
|
364
402
|
|
|
365
403
|
The agent fire-and-forgets `REGRESSION`, `PASS`, `ANOMALY`, `VISUAL_DIFF`, and `HEALTH_TRANSITION` events. Network errors never break the watcher (soft-fail).
|
|
366
404
|
|
|
367
|
-
See `
|
|
405
|
+
See `wupbro/README.md` for full API reference and driver endpoints (DOM diff, browserless, anomaly).
|
|
368
406
|
|
|
369
407
|
## Project Structure
|
|
370
408
|
|
|
@@ -372,19 +410,21 @@ See `wup-web/README.md` for full API reference and driver endpoints (DOM diff, b
|
|
|
372
410
|
wup/
|
|
373
411
|
├── wup/
|
|
374
412
|
│ ├── __init__.py # Package exports
|
|
375
|
-
│ ├──
|
|
413
|
+
│ ├── anomaly_detector.py # AnomalyDetector: hash, YAML structure, AST diff
|
|
414
|
+
│ ├── assistant.py # WupAssistant: interactive configuration wizard
|
|
415
|
+
│ ├── cli.py # CLI: watch, map-deps, status, init, testql-endpoints, assistant, version
|
|
376
416
|
│ ├── config.py # Config loading/saving + .wup.env support
|
|
377
417
|
│ ├── core.py # WupWatcher: detection, inference, scheduling
|
|
378
418
|
│ ├── dependency_mapper.py # DependencyMapper: codebase → deps.json
|
|
379
419
|
│ ├── testql_discovery.py # TestQLEndpointDiscovery: scenario parsing
|
|
380
420
|
│ ├── testql_watcher.py # TestQLWatcher: scenario runner + health tracking
|
|
381
421
|
│ ├── visual_diff.py # VisualDiffer: Playwright DOM snapshot + diff engine
|
|
382
|
-
│ ├── web_client.py # WebClient: async HTTP event sink →
|
|
422
|
+
│ ├── web_client.py # WebClient: async HTTP event sink → wupbro
|
|
383
423
|
│ └── models/
|
|
384
424
|
│ ├── __init__.py
|
|
385
|
-
│ └── config.py # Dataclasses: WupConfig, VisualDiffConfig, WebConfig...
|
|
386
|
-
├──
|
|
387
|
-
│ ├──
|
|
425
|
+
│ └── config.py # Dataclasses: WupConfig, ServiceConfig, WatchConfig, TestStrategyConfig, TestQLConfig, VisualDiffConfig, WebConfig, AnomalyDetectionConfig...
|
|
426
|
+
├── wupbro/ # Optional FastAPI dashboard (separate package)
|
|
427
|
+
│ ├── wupbro/
|
|
388
428
|
│ │ ├── main.py # FastAPI app
|
|
389
429
|
│ │ ├── routers/ # events, drivers, dashboard
|
|
390
430
|
│ │ ├── storage.py # EventStore (in-memory + JSONL)
|
|
@@ -436,6 +476,18 @@ python3 examples/visual_diff_demo.py http://localhost:8100/health
|
|
|
436
476
|
|
|
437
477
|
# TestQL + visual diff integration
|
|
438
478
|
python3 examples/testql_integration.py /path/to/project
|
|
479
|
+
|
|
480
|
+
# Monorepo analysis (c2004-style large projects)
|
|
481
|
+
python3 examples/c2004_monorepo_demo.py /path/to/monorepo
|
|
482
|
+
|
|
483
|
+
# CI/CD integration patterns
|
|
484
|
+
python3 examples/ci_cd_integration.py
|
|
485
|
+
|
|
486
|
+
# Generate GitHub Actions workflow
|
|
487
|
+
python3 examples/ci_cd_integration.py --generate-github-actions
|
|
488
|
+
|
|
489
|
+
# Webhook notifications (Slack, Teams, Discord)
|
|
490
|
+
python3 examples/webhook_notifications.py
|
|
439
491
|
```
|
|
440
492
|
|
|
441
493
|
### Building & Publishing
|
|
@@ -444,6 +496,39 @@ python3 examples/testql_integration.py /path/to/project
|
|
|
444
496
|
python -m build
|
|
445
497
|
```
|
|
446
498
|
|
|
499
|
+
## Real-World Testing
|
|
500
|
+
|
|
501
|
+
WUP has been tested on production-scale projects:
|
|
502
|
+
|
|
503
|
+
- **c2004 Project** (maskservice/c2004): Large IoT platform with 21+ connect-* modules
|
|
504
|
+
- 29 services auto-detected by assistant
|
|
505
|
+
- 100+ YAML configuration files monitored
|
|
506
|
+
- Anomaly detection: 0.06s for 5 config files (~1ms/file)
|
|
507
|
+
- Framework: Custom Python/FastAPI hybrid
|
|
508
|
+
|
|
509
|
+
## Documentation
|
|
510
|
+
|
|
511
|
+
Comprehensive documentation is available in the `docs/` directory:
|
|
512
|
+
|
|
513
|
+
- **[Configuration Assistant](docs/WUP_ASSISTANT.md)** - Interactive setup guide for `wup.yaml`
|
|
514
|
+
- `wup assistant` - Interactive configuration wizard
|
|
515
|
+
- Auto-detects framework and services
|
|
516
|
+
- Intelligent suggestions and validation
|
|
517
|
+
|
|
518
|
+
- **[Anomaly Detection](docs/ANOMALY_DETECTION.md)** - Fast alternatives to Playwright
|
|
519
|
+
- Hash-based change detection (~1ms per file)
|
|
520
|
+
- YAML structure analysis
|
|
521
|
+
- Python AST diff for API changes
|
|
522
|
+
- Configure with `anomaly_detection:` in wup.yaml
|
|
523
|
+
|
|
524
|
+
- **[Browser Notifications](docs/NOTIFICATIONS.md)** - Real-time alerts in wupbro
|
|
525
|
+
- 7 notification types (regressions, status changes, recoveries)
|
|
526
|
+
- Configurable per-type with cooldown
|
|
527
|
+
- Server-Sent Events for instant delivery
|
|
528
|
+
- Browser Notifications API integration
|
|
529
|
+
|
|
530
|
+
- **[TestQL Integration](docs/TESTQL_INTEGRATION.md)** - TestQL scenario support
|
|
531
|
+
|
|
447
532
|
## License
|
|
448
533
|
|
|
449
534
|
Licensed under Apache-2.0.
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
9
|
+
- 🤖 **LLM usage:** $4.8000 (32 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$913 (9.1h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
|
-
Generated on 2026-
|
|
12
|
+
Generated on 2026-05-01 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
|
|
|
@@ -48,16 +48,19 @@ pip install -e ".[dev]"
|
|
|
48
48
|
## Quick Start
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
# 1.
|
|
52
|
-
wup
|
|
51
|
+
# 1. Interactive configuration (recommended)
|
|
52
|
+
wup assistant
|
|
53
|
+
|
|
54
|
+
# 2. Or quick auto-setup
|
|
55
|
+
wup assistant --quick --template fastapi
|
|
53
56
|
|
|
54
|
-
#
|
|
57
|
+
# 3. Build dependency map (one-time setup)
|
|
55
58
|
wup map-deps ./my-project
|
|
56
59
|
|
|
57
|
-
#
|
|
60
|
+
# 4. Start watching for changes
|
|
58
61
|
wup watch ./my-project
|
|
59
62
|
|
|
60
|
-
#
|
|
63
|
+
# 5. Start with live dashboard
|
|
61
64
|
wup watch ./my-project --dashboard
|
|
62
65
|
```
|
|
63
66
|
|
|
@@ -160,6 +163,16 @@ Full regression: 15s test → 15% CPU spike
|
|
|
160
163
|
|
|
161
164
|
## Configuration
|
|
162
165
|
|
|
166
|
+
### Service Types
|
|
167
|
+
|
|
168
|
+
WUP supports three service types for coincidence detection and intelligent testing:
|
|
169
|
+
|
|
170
|
+
- **web** - HTTP/API services (FastAPI, Flask, Django, Express.js, etc.)
|
|
171
|
+
- **shell** - CLI tools, scripts, and command-line services
|
|
172
|
+
- **auto** - Automatic detection (default)
|
|
173
|
+
|
|
174
|
+
Coincidence detection allows WUP to identify related services. For example, if you have `users-web` and `users-shell`, WUP will detect they share the same domain and test both when relevant files change.
|
|
175
|
+
|
|
163
176
|
### wup.yaml Configuration File
|
|
164
177
|
|
|
165
178
|
WUP supports declarative configuration via `wup.yaml` (or `.wup.yaml`) in your project root. This allows you to define watch paths, service-specific settings, and test strategies.
|
|
@@ -170,6 +183,11 @@ Generate a default configuration:
|
|
|
170
183
|
wup init
|
|
171
184
|
```
|
|
172
185
|
|
|
186
|
+
The generated `wup.yaml` includes:
|
|
187
|
+
- **Metadata header**: Version, generation date, documentation links
|
|
188
|
+
- **Dependencies info**: WUP version and optional wupbro dashboard
|
|
189
|
+
- **Quick start guide**: Common commands to get started
|
|
190
|
+
|
|
173
191
|
Example `wup.yaml`:
|
|
174
192
|
|
|
175
193
|
```yaml
|
|
@@ -259,6 +277,26 @@ export WUP_CPU_THROTTLE=0.5
|
|
|
259
277
|
export WUP_DEBOUNCE=3
|
|
260
278
|
```
|
|
261
279
|
|
|
280
|
+
Full list of supported variables (see `.env.example`):
|
|
281
|
+
|
|
282
|
+
| Variable | Default | Description |
|
|
283
|
+
|----------|---------|-------------|
|
|
284
|
+
| `WUP_CPU_THROTTLE` | — | CPU usage threshold (0.0-1.0) |
|
|
285
|
+
| `WUP_DEBOUNCE` | — | Debounce time in seconds |
|
|
286
|
+
| `WUPBRO_ENDPOINT` | — | wupbro backend URL |
|
|
287
|
+
| `WUP_BASE_URL` | — | Base URL for visual diff page scanning |
|
|
288
|
+
| `OPENROUTER_API_KEY` | *(not set)* | Required for LLM features (https://openrouter.ai/keys) |
|
|
289
|
+
| `LLM_MODEL` | `openrouter/qwen/qwen3-coder-next` | LLM model for assistant |
|
|
290
|
+
| `PFIX_AUTO_APPLY` | `true` | Apply fixes without asking |
|
|
291
|
+
| `PFIX_AUTO_INSTALL_DEPS` | `true` | Auto pip/uv install missing deps |
|
|
292
|
+
| `PFIX_AUTO_RESTART` | `false` | Restart process after fix |
|
|
293
|
+
| `PFIX_MAX_RETRIES` | `3` | Max fix retries |
|
|
294
|
+
| `PFIX_DRY_RUN` | `false` | Dry-run mode (no changes written) |
|
|
295
|
+
| `PFIX_ENABLED` | `true` | Enable/disable pfix |
|
|
296
|
+
| `PFIX_GIT_COMMIT` | `false` | Auto-commit fixes |
|
|
297
|
+
| `PFIX_GIT_PREFIX` | `pfix:` | Commit message prefix |
|
|
298
|
+
| `PFIX_CREATE_BACKUPS` | `false` | Create .pfix_backups/ directory |
|
|
299
|
+
|
|
262
300
|
## Visual DOM Diff
|
|
263
301
|
|
|
264
302
|
WUP optionally scans configured pages with Playwright after each successful quick test, compares the DOM structure to the previous snapshot, and reports significant changes.
|
|
@@ -307,15 +345,15 @@ Visible in `wup status` as a "Visual DOM diffs" section.
|
|
|
307
345
|
|
|
308
346
|
If Playwright is not installed, the visual diff module logs a warning and skips scanning — it does **not** break the watcher.
|
|
309
347
|
|
|
310
|
-
## Web Dashboard (
|
|
348
|
+
## Web Dashboard (wupbro)
|
|
311
349
|
|
|
312
350
|
Optional FastAPI backend that receives events from WUP agents and renders a live dashboard.
|
|
313
351
|
|
|
314
352
|
### Run
|
|
315
353
|
|
|
316
354
|
```bash
|
|
317
|
-
pip install -e
|
|
318
|
-
|
|
355
|
+
pip install -e wupbro/
|
|
356
|
+
wupbro --reload --port 8000
|
|
319
357
|
```
|
|
320
358
|
|
|
321
359
|
Open <http://localhost:8000/> to see regressions, passes, anomalies, visual diffs, and health transitions in real time.
|
|
@@ -333,12 +371,12 @@ web:
|
|
|
333
371
|
Or via env:
|
|
334
372
|
|
|
335
373
|
```bash
|
|
336
|
-
export
|
|
374
|
+
export WUPBRO_ENDPOINT=http://localhost:8000
|
|
337
375
|
```
|
|
338
376
|
|
|
339
377
|
The agent fire-and-forgets `REGRESSION`, `PASS`, `ANOMALY`, `VISUAL_DIFF`, and `HEALTH_TRANSITION` events. Network errors never break the watcher (soft-fail).
|
|
340
378
|
|
|
341
|
-
See `
|
|
379
|
+
See `wupbro/README.md` for full API reference and driver endpoints (DOM diff, browserless, anomaly).
|
|
342
380
|
|
|
343
381
|
## Project Structure
|
|
344
382
|
|
|
@@ -346,19 +384,21 @@ See `wup-web/README.md` for full API reference and driver endpoints (DOM diff, b
|
|
|
346
384
|
wup/
|
|
347
385
|
├── wup/
|
|
348
386
|
│ ├── __init__.py # Package exports
|
|
349
|
-
│ ├──
|
|
387
|
+
│ ├── anomaly_detector.py # AnomalyDetector: hash, YAML structure, AST diff
|
|
388
|
+
│ ├── assistant.py # WupAssistant: interactive configuration wizard
|
|
389
|
+
│ ├── cli.py # CLI: watch, map-deps, status, init, testql-endpoints, assistant, version
|
|
350
390
|
│ ├── config.py # Config loading/saving + .wup.env support
|
|
351
391
|
│ ├── core.py # WupWatcher: detection, inference, scheduling
|
|
352
392
|
│ ├── dependency_mapper.py # DependencyMapper: codebase → deps.json
|
|
353
393
|
│ ├── testql_discovery.py # TestQLEndpointDiscovery: scenario parsing
|
|
354
394
|
│ ├── testql_watcher.py # TestQLWatcher: scenario runner + health tracking
|
|
355
395
|
│ ├── visual_diff.py # VisualDiffer: Playwright DOM snapshot + diff engine
|
|
356
|
-
│ ├── web_client.py # WebClient: async HTTP event sink →
|
|
396
|
+
│ ├── web_client.py # WebClient: async HTTP event sink → wupbro
|
|
357
397
|
│ └── models/
|
|
358
398
|
│ ├── __init__.py
|
|
359
|
-
│ └── config.py # Dataclasses: WupConfig, VisualDiffConfig, WebConfig...
|
|
360
|
-
├──
|
|
361
|
-
│ ├──
|
|
399
|
+
│ └── config.py # Dataclasses: WupConfig, ServiceConfig, WatchConfig, TestStrategyConfig, TestQLConfig, VisualDiffConfig, WebConfig, AnomalyDetectionConfig...
|
|
400
|
+
├── wupbro/ # Optional FastAPI dashboard (separate package)
|
|
401
|
+
│ ├── wupbro/
|
|
362
402
|
│ │ ├── main.py # FastAPI app
|
|
363
403
|
│ │ ├── routers/ # events, drivers, dashboard
|
|
364
404
|
│ │ ├── storage.py # EventStore (in-memory + JSONL)
|
|
@@ -410,6 +450,18 @@ python3 examples/visual_diff_demo.py http://localhost:8100/health
|
|
|
410
450
|
|
|
411
451
|
# TestQL + visual diff integration
|
|
412
452
|
python3 examples/testql_integration.py /path/to/project
|
|
453
|
+
|
|
454
|
+
# Monorepo analysis (c2004-style large projects)
|
|
455
|
+
python3 examples/c2004_monorepo_demo.py /path/to/monorepo
|
|
456
|
+
|
|
457
|
+
# CI/CD integration patterns
|
|
458
|
+
python3 examples/ci_cd_integration.py
|
|
459
|
+
|
|
460
|
+
# Generate GitHub Actions workflow
|
|
461
|
+
python3 examples/ci_cd_integration.py --generate-github-actions
|
|
462
|
+
|
|
463
|
+
# Webhook notifications (Slack, Teams, Discord)
|
|
464
|
+
python3 examples/webhook_notifications.py
|
|
413
465
|
```
|
|
414
466
|
|
|
415
467
|
### Building & Publishing
|
|
@@ -418,6 +470,39 @@ python3 examples/testql_integration.py /path/to/project
|
|
|
418
470
|
python -m build
|
|
419
471
|
```
|
|
420
472
|
|
|
473
|
+
## Real-World Testing
|
|
474
|
+
|
|
475
|
+
WUP has been tested on production-scale projects:
|
|
476
|
+
|
|
477
|
+
- **c2004 Project** (maskservice/c2004): Large IoT platform with 21+ connect-* modules
|
|
478
|
+
- 29 services auto-detected by assistant
|
|
479
|
+
- 100+ YAML configuration files monitored
|
|
480
|
+
- Anomaly detection: 0.06s for 5 config files (~1ms/file)
|
|
481
|
+
- Framework: Custom Python/FastAPI hybrid
|
|
482
|
+
|
|
483
|
+
## Documentation
|
|
484
|
+
|
|
485
|
+
Comprehensive documentation is available in the `docs/` directory:
|
|
486
|
+
|
|
487
|
+
- **[Configuration Assistant](docs/WUP_ASSISTANT.md)** - Interactive setup guide for `wup.yaml`
|
|
488
|
+
- `wup assistant` - Interactive configuration wizard
|
|
489
|
+
- Auto-detects framework and services
|
|
490
|
+
- Intelligent suggestions and validation
|
|
491
|
+
|
|
492
|
+
- **[Anomaly Detection](docs/ANOMALY_DETECTION.md)** - Fast alternatives to Playwright
|
|
493
|
+
- Hash-based change detection (~1ms per file)
|
|
494
|
+
- YAML structure analysis
|
|
495
|
+
- Python AST diff for API changes
|
|
496
|
+
- Configure with `anomaly_detection:` in wup.yaml
|
|
497
|
+
|
|
498
|
+
- **[Browser Notifications](docs/NOTIFICATIONS.md)** - Real-time alerts in wupbro
|
|
499
|
+
- 7 notification types (regressions, status changes, recoveries)
|
|
500
|
+
- Configurable per-type with cooldown
|
|
501
|
+
- Server-Sent Events for instant delivery
|
|
502
|
+
- Browser Notifications API integration
|
|
503
|
+
|
|
504
|
+
- **[TestQL Integration](docs/TESTQL_INTEGRATION.md)** - TestQL scenario support
|
|
505
|
+
|
|
421
506
|
## License
|
|
422
507
|
|
|
423
508
|
Licensed under Apache-2.0.
|
|
@@ -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.21"
|
|
11
11
|
__author__ = "Tom Sapletta"
|
|
12
12
|
|
|
13
13
|
from .config import load_config, save_config, get_default_config
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Python AST-based anomaly detection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from .anomaly_models import AnomalyResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ASTDetector:
|
|
14
|
+
"""Detect changes in Python files using AST comparison."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, snapshot_dir: Path):
|
|
17
|
+
self.snapshot_dir = snapshot_dir / 'ast_snapshots'
|
|
18
|
+
self.snapshot_dir.mkdir(parents=True, exist_ok=True)
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def _collect_import(node: ast.Import) -> List[str]:
|
|
22
|
+
return [f"import {alias.name}" for alias in node.names]
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def _collect_import_from(node: ast.ImportFrom) -> str:
|
|
26
|
+
module = node.module or ''
|
|
27
|
+
names = ', '.join(a.name for a in node.names)
|
|
28
|
+
return f"from {module} import {names}"
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def _collect_class(node: ast.ClassDef) -> Dict:
|
|
32
|
+
methods = [n.name for n in node.body if isinstance(n, ast.FunctionDef)]
|
|
33
|
+
bases = [ast.unparse(b) for b in node.bases] if hasattr(ast, 'unparse') else []
|
|
34
|
+
return {'name': node.name, 'methods': methods, 'bases': bases}
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def _collect_function(node: ast.FunctionDef) -> Dict:
|
|
38
|
+
return {'name': node.name, 'args': len(node.args.args),
|
|
39
|
+
'decorators': len(node.decorator_list)}
|
|
40
|
+
|
|
41
|
+
def _extract_ast_info(self, tree: ast.AST) -> Dict:
|
|
42
|
+
info: Dict = {'imports': [], 'classes': [], 'functions': [], 'top_level': []}
|
|
43
|
+
_handlers = {
|
|
44
|
+
ast.Import: lambda n: info['imports'].extend(self._collect_import(n)),
|
|
45
|
+
ast.ImportFrom: lambda n: info['imports'].append(self._collect_import_from(n)),
|
|
46
|
+
ast.ClassDef: lambda n: info['classes'].append(self._collect_class(n)),
|
|
47
|
+
ast.FunctionDef: lambda n: info['functions'].append(self._collect_function(n)),
|
|
48
|
+
}
|
|
49
|
+
for node in ast.iter_child_nodes(tree):
|
|
50
|
+
handler = _handlers.get(type(node))
|
|
51
|
+
if handler:
|
|
52
|
+
handler(node)
|
|
53
|
+
elif isinstance(node, ast.Assign):
|
|
54
|
+
for target in node.targets:
|
|
55
|
+
if isinstance(target, ast.Name):
|
|
56
|
+
info['top_level'].append(target.id)
|
|
57
|
+
return info
|
|
58
|
+
|
|
59
|
+
def _snapshot_path(self, file_path: Path) -> Path:
|
|
60
|
+
rel_path = str(file_path).replace('/', '_').replace('\\', '_')
|
|
61
|
+
return self.snapshot_dir / f"{rel_path}.ast.json"
|
|
62
|
+
|
|
63
|
+
def _compute_changes(self, old_info: Dict, new_info: Dict) -> List[str]:
|
|
64
|
+
changes: List[str] = []
|
|
65
|
+
old_classes = {c['name']: c for c in old_info.get('classes', [])}
|
|
66
|
+
new_classes = {c['name']: c for c in new_info.get('classes', [])}
|
|
67
|
+
|
|
68
|
+
for name in set(old_classes) | set(new_classes):
|
|
69
|
+
if name not in new_classes:
|
|
70
|
+
changes.append(f"Klasa usunięta: {name}")
|
|
71
|
+
elif name not in old_classes:
|
|
72
|
+
changes.append(f"Nowa klasa: {name}")
|
|
73
|
+
elif old_classes[name] != new_classes[name]:
|
|
74
|
+
changes.append(f"Klasa zmieniona: {name}")
|
|
75
|
+
|
|
76
|
+
old_funcs = {f['name'] for f in old_info.get('functions', [])}
|
|
77
|
+
new_funcs = {f['name'] for f in new_info.get('functions', [])}
|
|
78
|
+
for name in old_funcs - new_funcs:
|
|
79
|
+
changes.append(f"Funkcja usunięta: {name}")
|
|
80
|
+
for name in new_funcs - old_funcs:
|
|
81
|
+
changes.append(f"Nowa funkcja: {name}")
|
|
82
|
+
|
|
83
|
+
return changes
|
|
84
|
+
|
|
85
|
+
def detect(self, file_path: Path) -> Optional[AnomalyResult]:
|
|
86
|
+
"""Detect changes in Python file structure."""
|
|
87
|
+
if not str(file_path).endswith('.py'):
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
content = file_path.read_text(encoding='utf-8')
|
|
92
|
+
tree = ast.parse(content)
|
|
93
|
+
new_info = self._extract_ast_info(tree)
|
|
94
|
+
snap_path = self._snapshot_path(file_path)
|
|
95
|
+
|
|
96
|
+
if snap_path.exists():
|
|
97
|
+
old_info = json.loads(snap_path.read_text())
|
|
98
|
+
changes = self._compute_changes(old_info, new_info)
|
|
99
|
+
if changes:
|
|
100
|
+
snap_path.write_text(json.dumps(new_info, indent=2))
|
|
101
|
+
return AnomalyResult(
|
|
102
|
+
detector='ast',
|
|
103
|
+
file_path=str(file_path),
|
|
104
|
+
anomaly_type='changed',
|
|
105
|
+
severity='high',
|
|
106
|
+
message=f"Struktura Python zmieniona ({len(changes)} zmian)",
|
|
107
|
+
details={'changes': changes[:10]},
|
|
108
|
+
suggestions=["Przejrzyj zmiany w API przed deploymentem"],
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
snap_path.write_text(json.dumps(new_info, indent=2))
|
|
112
|
+
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
except SyntaxError as e:
|
|
116
|
+
return AnomalyResult(
|
|
117
|
+
detector='ast',
|
|
118
|
+
file_path=str(file_path),
|
|
119
|
+
anomaly_type='error',
|
|
120
|
+
severity='critical',
|
|
121
|
+
message=f"Błąd składni Python: {e}",
|
|
122
|
+
)
|
|
123
|
+
except Exception:
|
|
124
|
+
return None
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Fast hash-based anomaly detection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from .anomaly_models import AnomalyResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HashDetector:
|
|
13
|
+
"""Fast anomaly detection using file hashes."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, snapshot_dir: Path):
|
|
16
|
+
self.snapshot_dir = snapshot_dir / 'hash_snapshots'
|
|
17
|
+
self.snapshot_dir.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
|
|
19
|
+
def _compute_hash(self, content: str) -> str:
|
|
20
|
+
return hashlib.sha256(content.encode('utf-8')).hexdigest()[:16]
|
|
21
|
+
|
|
22
|
+
def _snapshot_path(self, file_path: Path) -> Path:
|
|
23
|
+
rel_path = str(file_path).replace('/', '_').replace('\\', '_')
|
|
24
|
+
return self.snapshot_dir / f"{rel_path}.hash"
|
|
25
|
+
|
|
26
|
+
def detect(self, file_path: Path) -> Optional[AnomalyResult]:
|
|
27
|
+
"""Detect changes using hash comparison."""
|
|
28
|
+
try:
|
|
29
|
+
if not file_path.exists():
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
content = file_path.read_text(encoding='utf-8')
|
|
33
|
+
current_hash = self._compute_hash(content)
|
|
34
|
+
snap_path = self._snapshot_path(file_path)
|
|
35
|
+
|
|
36
|
+
if snap_path.exists():
|
|
37
|
+
old_hash = snap_path.read_text().strip()
|
|
38
|
+
if old_hash != current_hash:
|
|
39
|
+
snap_path.write_text(current_hash)
|
|
40
|
+
return AnomalyResult(
|
|
41
|
+
detector='hash',
|
|
42
|
+
file_path=str(file_path),
|
|
43
|
+
anomaly_type='changed',
|
|
44
|
+
severity='medium',
|
|
45
|
+
message=f"Plik zmieniony (hash: {old_hash[:8]} → {current_hash[:8]})",
|
|
46
|
+
details={
|
|
47
|
+
'old_hash': old_hash,
|
|
48
|
+
'new_hash': current_hash,
|
|
49
|
+
'file_size': len(content),
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
else:
|
|
53
|
+
snap_path.write_text(current_hash)
|
|
54
|
+
return AnomalyResult(
|
|
55
|
+
detector='hash',
|
|
56
|
+
file_path=str(file_path),
|
|
57
|
+
anomaly_type='added',
|
|
58
|
+
severity='low',
|
|
59
|
+
message=f"Nowy plik wykryty (hash: {current_hash[:8]})",
|
|
60
|
+
details={'new_hash': current_hash},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
return AnomalyResult(
|
|
67
|
+
detector='hash',
|
|
68
|
+
file_path=str(file_path),
|
|
69
|
+
anomaly_type='error',
|
|
70
|
+
severity='low',
|
|
71
|
+
message=f"Błąd hash detection: {e}",
|
|
72
|
+
)
|