wup 0.2.1__tar.gz → 0.2.6__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.1 → wup-0.2.6}/PKG-INFO +131 -17
- {wup-0.2.1 → wup-0.2.6}/README.md +128 -14
- {wup-0.2.1 → wup-0.2.6}/pyproject.toml +3 -3
- {wup-0.2.1 → wup-0.2.6}/wup/__init__.py +1 -1
- {wup-0.2.1 → wup-0.2.6}/wup/cli.py +63 -0
- {wup-0.2.1 → wup-0.2.6}/wup/config.py +5 -2
- {wup-0.2.1 → wup-0.2.6}/wup/core.py +92 -7
- {wup-0.2.1 → wup-0.2.6}/wup/dependency_mapper.py +25 -0
- {wup-0.2.1 → wup-0.2.6}/wup/models/config.py +5 -2
- wup-0.2.6/wup/testql_discovery.py +229 -0
- {wup-0.2.1 → wup-0.2.6}/wup.egg-info/PKG-INFO +131 -17
- {wup-0.2.1 → wup-0.2.6}/wup.egg-info/SOURCES.txt +1 -0
- {wup-0.2.1 → wup-0.2.6}/LICENSE +0 -0
- {wup-0.2.1 → wup-0.2.6}/setup.cfg +0 -0
- {wup-0.2.1 → wup-0.2.6}/tests/test_testql_watcher.py +0 -0
- {wup-0.2.1 → wup-0.2.6}/tests/test_wup.py +0 -0
- {wup-0.2.1 → wup-0.2.6}/wup/models/__init__.py +0 -0
- {wup-0.2.1 → wup-0.2.6}/wup/testql_watcher.py +0 -0
- {wup-0.2.1 → wup-0.2.6}/wup.egg-info/dependency_links.txt +0 -0
- {wup-0.2.1 → wup-0.2.6}/wup.egg-info/entry_points.txt +0 -0
- {wup-0.2.1 → wup-0.2.6}/wup.egg-info/requires.txt +0 -0
- {wup-0.2.1 → wup-0.2.6}/wup.egg-info/top_level.txt +0 -0
{wup-0.2.1 → wup-0.2.6}/PKG-INFO
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
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
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
8
|
-
Project-URL: Repository, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/semcod/wup
|
|
8
|
+
Project-URL: Repository, https://github.com/semcod/wup
|
|
9
9
|
Keywords: wup,watcher,testing,regression,file-monitoring
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
@@ -28,17 +28,17 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
## AI Cost Tracking
|
|
30
30
|
|
|
31
|
-
    
|
|
32
|
+
  
|
|
33
33
|
|
|
34
|
-
- 🤖 **LLM usage:** $
|
|
34
|
+
- 🤖 **LLM usage:** $1.0500 (7 commits)
|
|
35
35
|
- 👤 **Human dev:** ~$200 (2.0h @ $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
|
|
|
@@ -56,6 +56,9 @@ WUP monitors file changes and runs intelligent regression tests using a 3-layer
|
|
|
56
56
|
- 🔍 **Dependency Mapping**: Automatic detection of files → endpoints → services
|
|
57
57
|
- 🚀 **Framework Support**: FastAPI, Flask, Django, Express.js, and more
|
|
58
58
|
- 📝 **Blame Reports**: Detailed regression reports with file/line/commit info
|
|
59
|
+
- ⚙️ **Configuration System**: Declarative configuration via `wup.yaml` file
|
|
60
|
+
- 🎛️ **Per-Service Settings**: Custom test strategies per service
|
|
61
|
+
- 🧪 **TestQL Integration**: Native support for TestQL scenarios
|
|
59
62
|
|
|
60
63
|
## Installation
|
|
61
64
|
|
|
@@ -70,13 +73,16 @@ pip install -e ".[dev]"
|
|
|
70
73
|
## Quick Start
|
|
71
74
|
|
|
72
75
|
```bash
|
|
73
|
-
# 1.
|
|
76
|
+
# 1. Initialize configuration (optional)
|
|
77
|
+
wup init
|
|
78
|
+
|
|
79
|
+
# 2. Build dependency map (one-time setup)
|
|
74
80
|
wup map-deps ./my-project
|
|
75
81
|
|
|
76
|
-
#
|
|
82
|
+
# 3. Start watching for changes
|
|
77
83
|
wup watch ./my-project
|
|
78
84
|
|
|
79
|
-
#
|
|
85
|
+
# 4. Start with live dashboard
|
|
80
86
|
wup watch ./my-project --dashboard
|
|
81
87
|
```
|
|
82
88
|
|
|
@@ -98,7 +104,7 @@ wup map-deps ./my-project --output my-deps.json
|
|
|
98
104
|
### Watch Project
|
|
99
105
|
|
|
100
106
|
```bash
|
|
101
|
-
# Basic watching
|
|
107
|
+
# Basic watching (uses wup.yaml if present)
|
|
102
108
|
wup watch ./my-project
|
|
103
109
|
|
|
104
110
|
# With custom settings
|
|
@@ -109,6 +115,25 @@ wup watch ./my-project \
|
|
|
109
115
|
|
|
110
116
|
# With live dashboard
|
|
111
117
|
wup watch ./my-project --dashboard
|
|
118
|
+
|
|
119
|
+
# Use specific config file
|
|
120
|
+
wup watch ./my-project --config custom-config.yaml
|
|
121
|
+
|
|
122
|
+
# TestQL mode
|
|
123
|
+
wup watch ./my-project --mode testql
|
|
124
|
+
|
|
125
|
+
# Discover endpoints from TestQL scenarios
|
|
126
|
+
wup testql-endpoints /path/to/scenarios --output testql-deps.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Initialize Configuration
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Generate default wup.yaml configuration
|
|
133
|
+
wup init
|
|
134
|
+
|
|
135
|
+
# Generate with custom output path
|
|
136
|
+
wup init --output .wup.yaml
|
|
112
137
|
```
|
|
113
138
|
|
|
114
139
|
### Check Status
|
|
@@ -127,24 +152,24 @@ wup status --deps my-deps.json
|
|
|
127
152
|
|
|
128
153
|
```
|
|
129
154
|
┌─────────────────────────────────────────────────────────────┐
|
|
130
|
-
│ DETECTION LAYER
|
|
155
|
+
│ DETECTION LAYER │
|
|
131
156
|
│ File watching with watchdog + heuristics │
|
|
132
157
|
│ Skips: .git, __pycache__, node_modules, .venv │
|
|
133
158
|
└──────────────────────┬──────────────────────────────────────┘
|
|
134
159
|
│ File change
|
|
135
160
|
▼
|
|
136
161
|
┌─────────────────────────────────────────────────────────────┐
|
|
137
|
-
│ PRIORITY LAYER
|
|
162
|
+
│ PRIORITY LAYER │
|
|
138
163
|
│ Quick test: 3 endpoints max per service │
|
|
139
|
-
│ Duration: ~1-2 seconds
|
|
140
|
-
│ Result: Pass → Done, Fail → Escalate
|
|
164
|
+
│ Duration: ~1-2 seconds │
|
|
165
|
+
│ Result: Pass → Done, Fail → Escalate │
|
|
141
166
|
└──────────────────────┬──────────────────────────────────────┘
|
|
142
167
|
│ Failure
|
|
143
168
|
▼
|
|
144
169
|
┌─────────────────────────────────────────────────────────────┐
|
|
145
|
-
│ DETAIL LAYER
|
|
170
|
+
│ DETAIL LAYER │
|
|
146
171
|
│ Full test: All endpoints with blame report │
|
|
147
|
-
│ Duration: ~3-5 seconds
|
|
172
|
+
│ Duration: ~3-5 seconds │
|
|
148
173
|
│ Result: Regression report with file/line/commit │
|
|
149
174
|
└─────────────────────────────────────────────────────────────┘
|
|
150
175
|
```
|
|
@@ -160,15 +185,94 @@ Full regression: 15s test → 15% CPU spike
|
|
|
160
185
|
|
|
161
186
|
## Configuration
|
|
162
187
|
|
|
188
|
+
### wup.yaml Configuration File
|
|
189
|
+
|
|
190
|
+
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.
|
|
191
|
+
|
|
192
|
+
Generate a default configuration:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
wup init
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Example `wup.yaml`:
|
|
199
|
+
|
|
200
|
+
```yaml
|
|
201
|
+
project:
|
|
202
|
+
name: "my-project"
|
|
203
|
+
description: "My awesome project"
|
|
204
|
+
|
|
205
|
+
watch:
|
|
206
|
+
# Folders to watch (supports glob patterns)
|
|
207
|
+
paths:
|
|
208
|
+
- "app/**"
|
|
209
|
+
- "src/**"
|
|
210
|
+
- "routes/**"
|
|
211
|
+
- "!tests/**" # exclusion
|
|
212
|
+
- "!node_modules/**"
|
|
213
|
+
|
|
214
|
+
# Global exclude patterns
|
|
215
|
+
exclude_patterns:
|
|
216
|
+
- "*.md"
|
|
217
|
+
- "*.txt"
|
|
218
|
+
- "migrations/**"
|
|
219
|
+
|
|
220
|
+
# File types to watch (empty = watch all files)
|
|
221
|
+
# Only changes to these file extensions will trigger tests
|
|
222
|
+
file_types:
|
|
223
|
+
- ".py"
|
|
224
|
+
- ".ts"
|
|
225
|
+
- ".jsx"
|
|
226
|
+
|
|
227
|
+
services:
|
|
228
|
+
# Service configurations - simplified with auto-detection
|
|
229
|
+
# If paths are empty, WUP auto-detects files by service name
|
|
230
|
+
|
|
231
|
+
- name: "users-shell"
|
|
232
|
+
type: "shell"
|
|
233
|
+
# Auto-detects files containing "users-shell"
|
|
234
|
+
|
|
235
|
+
- name: "users-web"
|
|
236
|
+
type: "web"
|
|
237
|
+
# Auto-detects files containing "users-web"
|
|
238
|
+
# Will detect coincidence with users-shell
|
|
239
|
+
|
|
240
|
+
# Or use explicit paths (old style still works)
|
|
241
|
+
- name: "payments"
|
|
242
|
+
paths:
|
|
243
|
+
- "app/payments/**"
|
|
244
|
+
type: "auto"
|
|
245
|
+
|
|
246
|
+
test_strategy:
|
|
247
|
+
quick:
|
|
248
|
+
debounce_s: 2
|
|
249
|
+
max_queue: 5
|
|
250
|
+
timeout_s: 10
|
|
251
|
+
detail:
|
|
252
|
+
debounce_s: 10
|
|
253
|
+
max_queue: 1
|
|
254
|
+
timeout_s: 30
|
|
255
|
+
|
|
256
|
+
testql:
|
|
257
|
+
# TestQL-specific configuration
|
|
258
|
+
scenario_dir: "scenarios/tests"
|
|
259
|
+
smoke_scenario: "smoke.testql.toon.yaml"
|
|
260
|
+
output_format: "json"
|
|
261
|
+
extra_args:
|
|
262
|
+
- "--timeout 10s"
|
|
263
|
+
```
|
|
264
|
+
|
|
163
265
|
### CLI Options
|
|
164
266
|
|
|
165
267
|
| Option | Default | Description |
|
|
166
268
|
|--------|---------|-------------|
|
|
269
|
+
| `--config` | auto | Path to wup.yaml config file |
|
|
167
270
|
| `--cpu-throttle` | 0.8 | CPU usage threshold (0.0-1.0) |
|
|
168
271
|
| `--debounce` | 2 | Debounce time in seconds |
|
|
169
272
|
| `--cooldown` | 300 | Test cooldown in seconds |
|
|
170
273
|
| `--dashboard` | false | Enable live dashboard |
|
|
171
274
|
| `--deps` | deps.json | Dependency map file path |
|
|
275
|
+
| `--mode` | default | Watcher mode: default or testql |
|
|
172
276
|
|
|
173
277
|
### Environment Variables
|
|
174
278
|
|
|
@@ -213,11 +317,21 @@ async def run_detail_test(self, service: str, endpoints: List[str]) -> Dict:
|
|
|
213
317
|
wup/
|
|
214
318
|
├── wup/
|
|
215
319
|
│ ├── __init__.py # Package exports
|
|
320
|
+
│ ├── config.py # Configuration loader
|
|
321
|
+
│ ├── models/
|
|
322
|
+
│ │ ├── __init__.py # Models package
|
|
323
|
+
│ │ └── config.py # Configuration dataclasses
|
|
216
324
|
│ ├── core.py # WupWatcher implementation
|
|
217
325
|
│ ├── dependency_mapper.py # Dependency mapping logic
|
|
326
|
+
│ ├── testql_watcher.py # TestQL integration
|
|
218
327
|
│ └── cli.py # CLI interface
|
|
219
328
|
├── tests/
|
|
220
329
|
│ └── test_wup.py # Unit tests
|
|
330
|
+
├── docs/
|
|
331
|
+
│ ├── 2.md # Refactoring documentation
|
|
332
|
+
│ ├── 3.md # Configuration plan
|
|
333
|
+
│ └── TESTQL_INTEGRATION.md # TestQL integration docs
|
|
334
|
+
├── wup.yaml.example # Example configuration
|
|
221
335
|
├── pyproject.toml # Package configuration
|
|
222
336
|
└── README.md # This file
|
|
223
337
|
```
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $
|
|
9
|
+
- 🤖 **LLM usage:** $1.0500 (7 commits)
|
|
10
10
|
- 👤 **Human dev:** ~$200 (2.0h @ $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
|
|
|
@@ -31,6 +31,9 @@ WUP monitors file changes and runs intelligent regression tests using a 3-layer
|
|
|
31
31
|
- 🔍 **Dependency Mapping**: Automatic detection of files → endpoints → services
|
|
32
32
|
- 🚀 **Framework Support**: FastAPI, Flask, Django, Express.js, and more
|
|
33
33
|
- 📝 **Blame Reports**: Detailed regression reports with file/line/commit info
|
|
34
|
+
- ⚙️ **Configuration System**: Declarative configuration via `wup.yaml` file
|
|
35
|
+
- 🎛️ **Per-Service Settings**: Custom test strategies per service
|
|
36
|
+
- 🧪 **TestQL Integration**: Native support for TestQL scenarios
|
|
34
37
|
|
|
35
38
|
## Installation
|
|
36
39
|
|
|
@@ -45,13 +48,16 @@ pip install -e ".[dev]"
|
|
|
45
48
|
## Quick Start
|
|
46
49
|
|
|
47
50
|
```bash
|
|
48
|
-
# 1.
|
|
51
|
+
# 1. Initialize configuration (optional)
|
|
52
|
+
wup init
|
|
53
|
+
|
|
54
|
+
# 2. Build dependency map (one-time setup)
|
|
49
55
|
wup map-deps ./my-project
|
|
50
56
|
|
|
51
|
-
#
|
|
57
|
+
# 3. Start watching for changes
|
|
52
58
|
wup watch ./my-project
|
|
53
59
|
|
|
54
|
-
#
|
|
60
|
+
# 4. Start with live dashboard
|
|
55
61
|
wup watch ./my-project --dashboard
|
|
56
62
|
```
|
|
57
63
|
|
|
@@ -73,7 +79,7 @@ wup map-deps ./my-project --output my-deps.json
|
|
|
73
79
|
### Watch Project
|
|
74
80
|
|
|
75
81
|
```bash
|
|
76
|
-
# Basic watching
|
|
82
|
+
# Basic watching (uses wup.yaml if present)
|
|
77
83
|
wup watch ./my-project
|
|
78
84
|
|
|
79
85
|
# With custom settings
|
|
@@ -84,6 +90,25 @@ wup watch ./my-project \
|
|
|
84
90
|
|
|
85
91
|
# With live dashboard
|
|
86
92
|
wup watch ./my-project --dashboard
|
|
93
|
+
|
|
94
|
+
# Use specific config file
|
|
95
|
+
wup watch ./my-project --config custom-config.yaml
|
|
96
|
+
|
|
97
|
+
# TestQL mode
|
|
98
|
+
wup watch ./my-project --mode testql
|
|
99
|
+
|
|
100
|
+
# Discover endpoints from TestQL scenarios
|
|
101
|
+
wup testql-endpoints /path/to/scenarios --output testql-deps.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Initialize Configuration
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Generate default wup.yaml configuration
|
|
108
|
+
wup init
|
|
109
|
+
|
|
110
|
+
# Generate with custom output path
|
|
111
|
+
wup init --output .wup.yaml
|
|
87
112
|
```
|
|
88
113
|
|
|
89
114
|
### Check Status
|
|
@@ -102,24 +127,24 @@ wup status --deps my-deps.json
|
|
|
102
127
|
|
|
103
128
|
```
|
|
104
129
|
┌─────────────────────────────────────────────────────────────┐
|
|
105
|
-
│ DETECTION LAYER
|
|
130
|
+
│ DETECTION LAYER │
|
|
106
131
|
│ File watching with watchdog + heuristics │
|
|
107
132
|
│ Skips: .git, __pycache__, node_modules, .venv │
|
|
108
133
|
└──────────────────────┬──────────────────────────────────────┘
|
|
109
134
|
│ File change
|
|
110
135
|
▼
|
|
111
136
|
┌─────────────────────────────────────────────────────────────┐
|
|
112
|
-
│ PRIORITY LAYER
|
|
137
|
+
│ PRIORITY LAYER │
|
|
113
138
|
│ Quick test: 3 endpoints max per service │
|
|
114
|
-
│ Duration: ~1-2 seconds
|
|
115
|
-
│ Result: Pass → Done, Fail → Escalate
|
|
139
|
+
│ Duration: ~1-2 seconds │
|
|
140
|
+
│ Result: Pass → Done, Fail → Escalate │
|
|
116
141
|
└──────────────────────┬──────────────────────────────────────┘
|
|
117
142
|
│ Failure
|
|
118
143
|
▼
|
|
119
144
|
┌─────────────────────────────────────────────────────────────┐
|
|
120
|
-
│ DETAIL LAYER
|
|
145
|
+
│ DETAIL LAYER │
|
|
121
146
|
│ Full test: All endpoints with blame report │
|
|
122
|
-
│ Duration: ~3-5 seconds
|
|
147
|
+
│ Duration: ~3-5 seconds │
|
|
123
148
|
│ Result: Regression report with file/line/commit │
|
|
124
149
|
└─────────────────────────────────────────────────────────────┘
|
|
125
150
|
```
|
|
@@ -135,15 +160,94 @@ Full regression: 15s test → 15% CPU spike
|
|
|
135
160
|
|
|
136
161
|
## Configuration
|
|
137
162
|
|
|
163
|
+
### wup.yaml Configuration File
|
|
164
|
+
|
|
165
|
+
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.
|
|
166
|
+
|
|
167
|
+
Generate a default configuration:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
wup init
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Example `wup.yaml`:
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
project:
|
|
177
|
+
name: "my-project"
|
|
178
|
+
description: "My awesome project"
|
|
179
|
+
|
|
180
|
+
watch:
|
|
181
|
+
# Folders to watch (supports glob patterns)
|
|
182
|
+
paths:
|
|
183
|
+
- "app/**"
|
|
184
|
+
- "src/**"
|
|
185
|
+
- "routes/**"
|
|
186
|
+
- "!tests/**" # exclusion
|
|
187
|
+
- "!node_modules/**"
|
|
188
|
+
|
|
189
|
+
# Global exclude patterns
|
|
190
|
+
exclude_patterns:
|
|
191
|
+
- "*.md"
|
|
192
|
+
- "*.txt"
|
|
193
|
+
- "migrations/**"
|
|
194
|
+
|
|
195
|
+
# File types to watch (empty = watch all files)
|
|
196
|
+
# Only changes to these file extensions will trigger tests
|
|
197
|
+
file_types:
|
|
198
|
+
- ".py"
|
|
199
|
+
- ".ts"
|
|
200
|
+
- ".jsx"
|
|
201
|
+
|
|
202
|
+
services:
|
|
203
|
+
# Service configurations - simplified with auto-detection
|
|
204
|
+
# If paths are empty, WUP auto-detects files by service name
|
|
205
|
+
|
|
206
|
+
- name: "users-shell"
|
|
207
|
+
type: "shell"
|
|
208
|
+
# Auto-detects files containing "users-shell"
|
|
209
|
+
|
|
210
|
+
- name: "users-web"
|
|
211
|
+
type: "web"
|
|
212
|
+
# Auto-detects files containing "users-web"
|
|
213
|
+
# Will detect coincidence with users-shell
|
|
214
|
+
|
|
215
|
+
# Or use explicit paths (old style still works)
|
|
216
|
+
- name: "payments"
|
|
217
|
+
paths:
|
|
218
|
+
- "app/payments/**"
|
|
219
|
+
type: "auto"
|
|
220
|
+
|
|
221
|
+
test_strategy:
|
|
222
|
+
quick:
|
|
223
|
+
debounce_s: 2
|
|
224
|
+
max_queue: 5
|
|
225
|
+
timeout_s: 10
|
|
226
|
+
detail:
|
|
227
|
+
debounce_s: 10
|
|
228
|
+
max_queue: 1
|
|
229
|
+
timeout_s: 30
|
|
230
|
+
|
|
231
|
+
testql:
|
|
232
|
+
# TestQL-specific configuration
|
|
233
|
+
scenario_dir: "scenarios/tests"
|
|
234
|
+
smoke_scenario: "smoke.testql.toon.yaml"
|
|
235
|
+
output_format: "json"
|
|
236
|
+
extra_args:
|
|
237
|
+
- "--timeout 10s"
|
|
238
|
+
```
|
|
239
|
+
|
|
138
240
|
### CLI Options
|
|
139
241
|
|
|
140
242
|
| Option | Default | Description |
|
|
141
243
|
|--------|---------|-------------|
|
|
244
|
+
| `--config` | auto | Path to wup.yaml config file |
|
|
142
245
|
| `--cpu-throttle` | 0.8 | CPU usage threshold (0.0-1.0) |
|
|
143
246
|
| `--debounce` | 2 | Debounce time in seconds |
|
|
144
247
|
| `--cooldown` | 300 | Test cooldown in seconds |
|
|
145
248
|
| `--dashboard` | false | Enable live dashboard |
|
|
146
249
|
| `--deps` | deps.json | Dependency map file path |
|
|
250
|
+
| `--mode` | default | Watcher mode: default or testql |
|
|
147
251
|
|
|
148
252
|
### Environment Variables
|
|
149
253
|
|
|
@@ -188,11 +292,21 @@ async def run_detail_test(self, service: str, endpoints: List[str]) -> Dict:
|
|
|
188
292
|
wup/
|
|
189
293
|
├── wup/
|
|
190
294
|
│ ├── __init__.py # Package exports
|
|
295
|
+
│ ├── config.py # Configuration loader
|
|
296
|
+
│ ├── models/
|
|
297
|
+
│ │ ├── __init__.py # Models package
|
|
298
|
+
│ │ └── config.py # Configuration dataclasses
|
|
191
299
|
│ ├── core.py # WupWatcher implementation
|
|
192
300
|
│ ├── dependency_mapper.py # Dependency mapping logic
|
|
301
|
+
│ ├── testql_watcher.py # TestQL integration
|
|
193
302
|
│ └── cli.py # CLI interface
|
|
194
303
|
├── tests/
|
|
195
304
|
│ └── test_wup.py # Unit tests
|
|
305
|
+
├── docs/
|
|
306
|
+
│ ├── 2.md # Refactoring documentation
|
|
307
|
+
│ ├── 3.md # Configuration plan
|
|
308
|
+
│ └── TESTQL_INTEGRATION.md # TestQL integration docs
|
|
309
|
+
├── wup.yaml.example # Example configuration
|
|
196
310
|
├── pyproject.toml # Package configuration
|
|
197
311
|
└── README.md # This file
|
|
198
312
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wup"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.6"
|
|
8
8
|
description = "WUP (What's Up) - Intelligent file watcher for regression testing in large projects"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -33,8 +33,8 @@ classifiers = [
|
|
|
33
33
|
wup = "wup.cli:app"
|
|
34
34
|
|
|
35
35
|
[project.urls]
|
|
36
|
-
Homepage = "https://github.com/
|
|
37
|
-
Repository = "https://github.com/
|
|
36
|
+
Homepage = "https://github.com/semcod/wup"
|
|
37
|
+
Repository = "https://github.com/semcod/wup"
|
|
38
38
|
|
|
39
39
|
[tool.pfix]
|
|
40
40
|
# Self-healing Python configuration
|
|
@@ -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.6"
|
|
11
11
|
__author__ = "Tom Sapletta"
|
|
12
12
|
|
|
13
13
|
from .config import load_config, save_config, get_default_config
|
|
@@ -263,6 +263,69 @@ def init(
|
|
|
263
263
|
console.print(f"[dim]Edit this file to customize your WUP setup[/dim]")
|
|
264
264
|
|
|
265
265
|
|
|
266
|
+
@app.command()
|
|
267
|
+
def testql_endpoints(
|
|
268
|
+
scenarios_dir: str = typer.Argument(..., help="Path to TestQL scenarios directory"),
|
|
269
|
+
output: str = typer.Option("testql-deps.json", "--output", "-o", help="Output dependency map file path"),
|
|
270
|
+
testql_bin: str = typer.Option("testql", "--testql-bin", help="TestQL executable name or path"),
|
|
271
|
+
):
|
|
272
|
+
"""
|
|
273
|
+
Discover endpoints from TestQL scenario files and build dependency map.
|
|
274
|
+
"""
|
|
275
|
+
from .testql_discovery import TestQLEndpointDiscovery
|
|
276
|
+
from rich.table import Table
|
|
277
|
+
|
|
278
|
+
scenarios_path = Path(scenarios_dir)
|
|
279
|
+
|
|
280
|
+
if not scenarios_path.exists():
|
|
281
|
+
console.print(f"[red]Error: Scenarios directory '{scenarios_dir}' does not exist[/red]")
|
|
282
|
+
raise typer.Exit(1)
|
|
283
|
+
|
|
284
|
+
console.print(f"[cyan]🔍 Discovering endpoints from TestQL scenarios...[/cyan]")
|
|
285
|
+
console.print(f"[dim]Scenarios directory: {scenarios_dir}[/dim]")
|
|
286
|
+
console.print()
|
|
287
|
+
|
|
288
|
+
discovery = TestQLEndpointDiscovery(scenarios_dir, testql_bin)
|
|
289
|
+
dependency_map = discovery.to_dependency_map()
|
|
290
|
+
|
|
291
|
+
# Display results
|
|
292
|
+
table = Table(title="Discovered Endpoints")
|
|
293
|
+
table.add_column("Service", style="cyan")
|
|
294
|
+
table.add_column("Endpoints", style="green")
|
|
295
|
+
table.add_column("Scenarios", style="yellow")
|
|
296
|
+
|
|
297
|
+
total_endpoints = 0
|
|
298
|
+
total_scenarios = 0
|
|
299
|
+
|
|
300
|
+
for service, info in sorted(dependency_map.get("services", {}).items()):
|
|
301
|
+
endpoints_count = len(info.get("endpoints", []))
|
|
302
|
+
scenarios_count = len(info.get("scenarios", []))
|
|
303
|
+
total_endpoints += endpoints_count
|
|
304
|
+
total_scenarios += scenarios_count
|
|
305
|
+
|
|
306
|
+
table.add_row(
|
|
307
|
+
service,
|
|
308
|
+
str(endpoints_count),
|
|
309
|
+
str(scenarios_count)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
console.print(table)
|
|
313
|
+
console.print()
|
|
314
|
+
console.print(f"[bold]Summary:[/bold]")
|
|
315
|
+
console.print(f" Services: {len(dependency_map.get('services', {}))}")
|
|
316
|
+
console.print(f" Total endpoints: {total_endpoints}")
|
|
317
|
+
console.print(f" Total scenarios: {total_scenarios}")
|
|
318
|
+
console.print()
|
|
319
|
+
|
|
320
|
+
# Save to file
|
|
321
|
+
import json
|
|
322
|
+
output_path = Path(output)
|
|
323
|
+
with open(output_path, 'w') as f:
|
|
324
|
+
json.dump(dependency_map, f, indent=2)
|
|
325
|
+
|
|
326
|
+
console.print(f"[green]✓ Dependency map saved to {output_path}[/green]")
|
|
327
|
+
|
|
328
|
+
|
|
266
329
|
@app.command()
|
|
267
330
|
def version():
|
|
268
331
|
"""Show WUP version."""
|
|
@@ -99,7 +99,8 @@ def validate_config(raw: dict) -> WupConfig:
|
|
|
99
99
|
watch_raw = raw.get("watch", {})
|
|
100
100
|
watch = WatchConfig(
|
|
101
101
|
paths=watch_raw.get("paths", []),
|
|
102
|
-
exclude_patterns=watch_raw.get("exclude_patterns", ["*.md", "*.txt"])
|
|
102
|
+
exclude_patterns=watch_raw.get("exclude_patterns", ["*.md", "*.txt"]),
|
|
103
|
+
file_types=watch_raw.get("file_types", [])
|
|
103
104
|
)
|
|
104
105
|
|
|
105
106
|
# Parse services
|
|
@@ -117,6 +118,7 @@ def validate_config(raw: dict) -> WupConfig:
|
|
|
117
118
|
name=svc_raw["name"],
|
|
118
119
|
root=svc_raw.get("root", ""),
|
|
119
120
|
paths=svc_raw.get("paths", []),
|
|
121
|
+
type=svc_raw.get("type", "auto"),
|
|
120
122
|
quick_tests=ServiceTestConfig(
|
|
121
123
|
scope=quick_tests_raw.get("scope", "all"),
|
|
122
124
|
max_endpoints=quick_tests_raw.get("max_endpoints", 10)
|
|
@@ -147,7 +149,8 @@ def validate_config(raw: dict) -> WupConfig:
|
|
|
147
149
|
scenario_dir=testql_raw.get("scenario_dir", "scenarios/tests"),
|
|
148
150
|
smoke_scenario=testql_raw.get("smoke_scenario", "smoke.testql.toon.yaml"),
|
|
149
151
|
output_format=testql_raw.get("output_format", "json"),
|
|
150
|
-
extra_args=testql_raw.get("extra_args", ["--timeout 10s"])
|
|
152
|
+
extra_args=testql_raw.get("extra_args", ["--timeout 10s"]),
|
|
153
|
+
endpoint_discovery=testql_raw.get("endpoint_discovery", True)
|
|
151
154
|
)
|
|
152
155
|
|
|
153
156
|
return WupConfig(
|
|
@@ -107,9 +107,7 @@ class WupWatcher:
|
|
|
107
107
|
"""
|
|
108
108
|
Infer service name from file path.
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
app/users/routes.py → "app/users"
|
|
112
|
-
src/components/auth.ts → "src/components"
|
|
110
|
+
Uses config services first, then dependency mapper, then heuristics.
|
|
113
111
|
"""
|
|
114
112
|
rel_path = self._to_relative_path(file_path)
|
|
115
113
|
parts = rel_path.parts
|
|
@@ -117,10 +115,17 @@ class WupWatcher:
|
|
|
117
115
|
# Try to match against configured services first
|
|
118
116
|
if self.config.services:
|
|
119
117
|
for svc in self.config.services:
|
|
120
|
-
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
if svc.paths:
|
|
119
|
+
# Use explicit paths if provided
|
|
120
|
+
for svc_path in svc.paths:
|
|
121
|
+
if str(rel_path).startswith(svc_path.replace("**", "")):
|
|
122
|
+
return svc.name
|
|
123
|
+
else:
|
|
124
|
+
# Auto-detect: check if service name appears in path
|
|
125
|
+
service_name_parts = svc.name.replace("/", " ").replace("-", " ").split()
|
|
126
|
+
for part in service_name_parts:
|
|
127
|
+
if part.lower() in str(rel_path).lower():
|
|
128
|
+
return svc.name
|
|
124
129
|
|
|
125
130
|
# Use dependency mapper if available
|
|
126
131
|
service = self.dependency_mapper.get_service_for_file(file_path)
|
|
@@ -133,6 +138,75 @@ class WupWatcher:
|
|
|
133
138
|
|
|
134
139
|
return None
|
|
135
140
|
|
|
141
|
+
def detect_service_coincidences(self, changed_service: str) -> List[str]:
|
|
142
|
+
"""
|
|
143
|
+
Detect coincidences between services (e.g., shell <-> web).
|
|
144
|
+
|
|
145
|
+
When a service changes, this finds related services that should also be tested.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
changed_service: The service that changed
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of related services that should also be tested
|
|
152
|
+
"""
|
|
153
|
+
related_services = []
|
|
154
|
+
|
|
155
|
+
if not self.config.services:
|
|
156
|
+
return related_services
|
|
157
|
+
|
|
158
|
+
# Get the changed service config
|
|
159
|
+
changed_svc_config = None
|
|
160
|
+
for svc in self.config.services:
|
|
161
|
+
if svc.name == changed_service:
|
|
162
|
+
changed_svc_config = svc
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
if not changed_svc_config:
|
|
166
|
+
return related_services
|
|
167
|
+
|
|
168
|
+
# Find coincidences based on service type
|
|
169
|
+
for svc in self.config.services:
|
|
170
|
+
if svc.name == changed_service:
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
# Coincidence: shell <-> web for same domain
|
|
174
|
+
if changed_svc_config.type != "auto" and svc.type != "auto":
|
|
175
|
+
# If both have explicit types, check for opposites
|
|
176
|
+
if (changed_svc_config.type == "shell" and svc.type == "web") or \
|
|
177
|
+
(changed_svc_config.type == "web" and svc.type == "shell"):
|
|
178
|
+
# Check if they share a common domain (same base name)
|
|
179
|
+
if self._services_share_domain(changed_service, svc.name):
|
|
180
|
+
related_services.append(svc.name)
|
|
181
|
+
|
|
182
|
+
# Coincidence: auto-detect by name similarity
|
|
183
|
+
elif changed_svc_config.type == "auto" or svc.type == "auto":
|
|
184
|
+
if self._services_share_domain(changed_service, svc.name):
|
|
185
|
+
related_services.append(svc.name)
|
|
186
|
+
|
|
187
|
+
return related_services
|
|
188
|
+
|
|
189
|
+
def _services_share_domain(self, service1: str, service2: str) -> bool:
|
|
190
|
+
"""
|
|
191
|
+
Check if two services share a common domain/base name.
|
|
192
|
+
|
|
193
|
+
Examples:
|
|
194
|
+
users-shell and users-web -> True
|
|
195
|
+
api/auth and api/users -> False
|
|
196
|
+
payments and payments-shell -> True
|
|
197
|
+
"""
|
|
198
|
+
# Extract base names (remove type suffixes like -shell, -web)
|
|
199
|
+
def extract_base(name: str) -> str:
|
|
200
|
+
for suffix in ["-shell", "-web", "_shell", "_web"]:
|
|
201
|
+
if name.endswith(suffix):
|
|
202
|
+
return name[:-len(suffix)]
|
|
203
|
+
return name
|
|
204
|
+
|
|
205
|
+
base1 = extract_base(service1)
|
|
206
|
+
base2 = extract_base(service2)
|
|
207
|
+
|
|
208
|
+
return base1 == base2
|
|
209
|
+
|
|
136
210
|
def get_service_config(self, service_name: str) -> Optional[ServiceConfig]:
|
|
137
211
|
"""
|
|
138
212
|
Get service configuration by name.
|
|
@@ -316,6 +390,17 @@ class WupWatcher:
|
|
|
316
390
|
if pattern in str(rel_path):
|
|
317
391
|
return
|
|
318
392
|
|
|
393
|
+
# Filter by file type if specified in config
|
|
394
|
+
if self.config.watch.file_types:
|
|
395
|
+
# Ensure file extensions start with dot
|
|
396
|
+
file_ext = rel_path.suffix if rel_path.suffix else ""
|
|
397
|
+
if not file_ext.startswith("."):
|
|
398
|
+
file_ext = f".{file_ext}"
|
|
399
|
+
|
|
400
|
+
# Check if file extension matches any of the configured types
|
|
401
|
+
if file_ext not in self.config.watch.file_types:
|
|
402
|
+
return
|
|
403
|
+
|
|
319
404
|
# Infer service from file path
|
|
320
405
|
service = self.infer_service(file_path)
|
|
321
406
|
|
|
@@ -14,6 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
from typing import Dict, List, Set, Optional
|
|
15
15
|
from collections import defaultdict
|
|
16
16
|
import re
|
|
17
|
+
from .testql_discovery import TestQLEndpointDiscovery
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class DependencyMapper:
|
|
@@ -257,3 +258,27 @@ class DependencyMapper:
|
|
|
257
258
|
self.service_to_files[service] = set(info.get("files", []))
|
|
258
259
|
|
|
259
260
|
self.file_to_endpoints = defaultdict(list, data.get("files", {}))
|
|
261
|
+
|
|
262
|
+
def build_from_testql_scenarios(self, scenarios_dir: str, testql_bin: str = "testql") -> Dict:
|
|
263
|
+
"""
|
|
264
|
+
Build dependency map from TestQL scenario files.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
scenarios_dir: Path to TestQL scenarios directory
|
|
268
|
+
testql_bin: TestQL executable name or path
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Dictionary containing the full dependency map
|
|
272
|
+
"""
|
|
273
|
+
discovery = TestQLEndpointDiscovery(scenarios_dir, testql_bin)
|
|
274
|
+
dependency_map = discovery.to_dependency_map()
|
|
275
|
+
|
|
276
|
+
# Merge with existing mappings
|
|
277
|
+
for service, info in dependency_map.get("services", {}).items():
|
|
278
|
+
self.service_to_endpoints[service].extend(info["endpoints"])
|
|
279
|
+
self.service_to_files[service].update(info["files"])
|
|
280
|
+
|
|
281
|
+
for file_path, endpoints in dependency_map.get("files", {}).items():
|
|
282
|
+
self.file_to_endpoints[file_path].extend(endpoints)
|
|
283
|
+
|
|
284
|
+
return self.to_dict()
|
|
@@ -27,8 +27,9 @@ class ServiceTestConfig:
|
|
|
27
27
|
class ServiceConfig:
|
|
28
28
|
"""Configuration for a single service."""
|
|
29
29
|
name: str
|
|
30
|
-
root: str
|
|
31
|
-
paths: List[str] = field(default_factory=list)
|
|
30
|
+
root: str = "" # Optional - auto-detected if empty
|
|
31
|
+
paths: List[str] = field(default_factory=list) # Optional - auto-detected if empty
|
|
32
|
+
type: str = "auto" # "web", "shell", "auto" - for coincidence detection
|
|
32
33
|
quick_tests: ServiceTestConfig = field(default_factory=ServiceTestConfig)
|
|
33
34
|
detail_tests: ServiceTestConfig = field(default_factory=ServiceTestConfig)
|
|
34
35
|
cpu_throttle: float = 0.8
|
|
@@ -40,6 +41,7 @@ class WatchConfig:
|
|
|
40
41
|
"""Configuration for file watching."""
|
|
41
42
|
paths: List[str] = field(default_factory=list)
|
|
42
43
|
exclude_patterns: List[str] = field(default_factory=lambda: ["*.md", "*.txt"])
|
|
44
|
+
file_types: List[str] = field(default_factory=list) # e.g., [".py", ".ts", ".jsx"]
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
@dataclass
|
|
@@ -56,6 +58,7 @@ class TestQLConfig:
|
|
|
56
58
|
smoke_scenario: str = "smoke.testql.toon.yaml"
|
|
57
59
|
output_format: str = "json"
|
|
58
60
|
extra_args: List[str] = field(default_factory=lambda: ["--timeout 10s"])
|
|
61
|
+
endpoint_discovery: bool = True # Enable automatic endpoint discovery from scenarios
|
|
59
62
|
|
|
60
63
|
|
|
61
64
|
@dataclass
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TestQL endpoint discovery module.
|
|
3
|
+
|
|
4
|
+
Discovers API endpoints from TestQL scenario files (.testql.toon.yaml)
|
|
5
|
+
and maps them to services for intelligent testing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List, Optional, Set
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestQLEndpointDiscovery:
|
|
17
|
+
"""Discover endpoints from TestQL scenario files."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, scenarios_dir: str, testql_bin: str = "testql"):
|
|
20
|
+
"""
|
|
21
|
+
Initialize TestQL endpoint discovery.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
scenarios_dir: Path to TestQL scenarios directory
|
|
25
|
+
testql_bin: TestQL executable name or path
|
|
26
|
+
"""
|
|
27
|
+
self.scenarios_dir = Path(scenarios_dir)
|
|
28
|
+
self.testql_bin = testql_bin
|
|
29
|
+
self.endpoints_by_service: Dict[str, Set[str]] = {}
|
|
30
|
+
self.scenarios_by_service: Dict[str, List[Path]] = {}
|
|
31
|
+
|
|
32
|
+
def discover_scenarios(self) -> List[Path]:
|
|
33
|
+
"""
|
|
34
|
+
Find all TestQL scenario files.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
List of paths to .testql.toon.yaml files
|
|
38
|
+
"""
|
|
39
|
+
if not self.scenarios_dir.exists():
|
|
40
|
+
return []
|
|
41
|
+
|
|
42
|
+
return sorted(self.scenarios_dir.rglob("*.testql.toon.yaml"))
|
|
43
|
+
|
|
44
|
+
def parse_scenario_endpoints(self, scenario_path: Path) -> List[str]:
|
|
45
|
+
"""
|
|
46
|
+
Extract endpoints from a TestQL scenario file.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
scenario_path: Path to scenario file
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
List of endpoint paths found in the scenario
|
|
53
|
+
"""
|
|
54
|
+
endpoints = []
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
with open(scenario_path, 'r') as f:
|
|
58
|
+
content = f.read()
|
|
59
|
+
|
|
60
|
+
# Parse TestQL API blocks
|
|
61
|
+
# Pattern: METHOD, /path
|
|
62
|
+
api_pattern = re.compile(r'^\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*,\s*([^\s,]+)', re.MULTILINE)
|
|
63
|
+
matches = api_pattern.findall(content)
|
|
64
|
+
|
|
65
|
+
for method, path in matches:
|
|
66
|
+
endpoints.append(f"{method.upper()} {path}")
|
|
67
|
+
|
|
68
|
+
# Also try parsing as YAML to extract structured data
|
|
69
|
+
try:
|
|
70
|
+
data = yaml.safe_load(content)
|
|
71
|
+
if data and isinstance(data, dict):
|
|
72
|
+
# Look for API sections
|
|
73
|
+
if 'API' in data:
|
|
74
|
+
api_data = data['API']
|
|
75
|
+
if isinstance(api_data, list):
|
|
76
|
+
for item in api_data:
|
|
77
|
+
if isinstance(item, (list, tuple)) and len(item) >= 2:
|
|
78
|
+
method = item[0]
|
|
79
|
+
path = item[1]
|
|
80
|
+
endpoints.append(f"{method.upper()} {path}")
|
|
81
|
+
except:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f"Warning: Could not parse {scenario_path}: {e}")
|
|
86
|
+
|
|
87
|
+
return list(set(endpoints)) # Remove duplicates
|
|
88
|
+
|
|
89
|
+
def infer_service_from_scenario(self, scenario_path: Path) -> Optional[str]:
|
|
90
|
+
"""
|
|
91
|
+
Infer service name from scenario file path.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
scenario_path: Path to scenario file
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Service name or None
|
|
98
|
+
"""
|
|
99
|
+
# Extract service from path
|
|
100
|
+
# e.g., scenarios/tests/users/users-api.testql.toon.yaml -> users
|
|
101
|
+
rel_path = scenario_path.relative_to(self.scenarios_dir)
|
|
102
|
+
parts = rel_path.parts
|
|
103
|
+
|
|
104
|
+
# Look for service-like patterns in the path
|
|
105
|
+
for part in parts:
|
|
106
|
+
if part not in ['tests', 'scenarios', 'api', 'views']:
|
|
107
|
+
# Clean up the name
|
|
108
|
+
service = part.replace('-', '/').replace('_', '/')
|
|
109
|
+
return service
|
|
110
|
+
|
|
111
|
+
# Fallback to parent directory name
|
|
112
|
+
if scenario_path.parent.name != self.scenarios_dir.name:
|
|
113
|
+
return scenario_path.parent.name
|
|
114
|
+
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
def discover_all_endpoints(self) -> Dict[str, Dict]:
|
|
118
|
+
"""
|
|
119
|
+
Discover all endpoints from scenarios.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Dictionary mapping service names to endpoint info:
|
|
123
|
+
{
|
|
124
|
+
"service_name": {
|
|
125
|
+
"endpoints": ["GET /api/users", ...],
|
|
126
|
+
"scenarios": [path1, path2, ...]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
"""
|
|
130
|
+
scenarios = self.discover_scenarios()
|
|
131
|
+
|
|
132
|
+
for scenario in scenarios:
|
|
133
|
+
endpoints = self.parse_scenario_endpoints(scenario)
|
|
134
|
+
service = self.infer_service_from_scenario(scenario)
|
|
135
|
+
|
|
136
|
+
if not service:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
if service not in self.endpoints_by_service:
|
|
140
|
+
self.endpoints_by_service[service] = set()
|
|
141
|
+
self.scenarios_by_service[service] = []
|
|
142
|
+
|
|
143
|
+
self.endpoints_by_service[service].update(endpoints)
|
|
144
|
+
self.scenarios_by_service[service].append(scenario)
|
|
145
|
+
|
|
146
|
+
# Convert to output format
|
|
147
|
+
result = {}
|
|
148
|
+
for service in self.endpoints_by_service:
|
|
149
|
+
result[service] = {
|
|
150
|
+
"endpoints": sorted(list(self.endpoints_by_service[service])),
|
|
151
|
+
"scenarios": [str(s) for s in self.scenarios_by_service[service]]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return result
|
|
155
|
+
|
|
156
|
+
def discover_via_testql_cli(self, service: Optional[str] = None) -> List[str]:
|
|
157
|
+
"""
|
|
158
|
+
Use TestQL CLI to discover endpoints.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
service: Optional service name to filter
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
List of discovered endpoints
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
cmd = [self.testql_bin, "endpoints", str(self.scenarios_dir)]
|
|
168
|
+
if service:
|
|
169
|
+
cmd.extend(["--service", service])
|
|
170
|
+
|
|
171
|
+
result = subprocess.run(
|
|
172
|
+
cmd,
|
|
173
|
+
capture_output=True,
|
|
174
|
+
text=True,
|
|
175
|
+
timeout=30
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if result.returncode == 0:
|
|
179
|
+
# Parse output - assuming one endpoint per line
|
|
180
|
+
endpoints = [line.strip() for line in result.stdout.split('\n') if line.strip()]
|
|
181
|
+
return endpoints
|
|
182
|
+
else:
|
|
183
|
+
print(f"TestQL CLI error: {result.stderr}")
|
|
184
|
+
return []
|
|
185
|
+
|
|
186
|
+
except FileNotFoundError:
|
|
187
|
+
print(f"TestQL binary '{self.testql_bin}' not found. Using file-based discovery.")
|
|
188
|
+
return []
|
|
189
|
+
except subprocess.TimeoutExpired:
|
|
190
|
+
print("TestQL CLI timeout. Using file-based discovery.")
|
|
191
|
+
return []
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print(f"TestQL CLI error: {e}")
|
|
194
|
+
return []
|
|
195
|
+
|
|
196
|
+
def to_dependency_map(self) -> Dict:
|
|
197
|
+
"""
|
|
198
|
+
Convert discovered endpoints to dependency map format.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Dictionary in dependency map format:
|
|
202
|
+
{
|
|
203
|
+
"services": {
|
|
204
|
+
"service_name": {
|
|
205
|
+
"endpoints": [...],
|
|
206
|
+
"files": [...]
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
"files": {...}
|
|
210
|
+
}
|
|
211
|
+
"""
|
|
212
|
+
discovery = self.discover_all_endpoints()
|
|
213
|
+
|
|
214
|
+
dependency_map = {
|
|
215
|
+
"services": {},
|
|
216
|
+
"files": {}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for service, info in discovery.items():
|
|
220
|
+
dependency_map["services"][service] = {
|
|
221
|
+
"endpoints": info["endpoints"],
|
|
222
|
+
"files": [str(s) for s in info["scenarios"]]
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# Map files to endpoints
|
|
226
|
+
for scenario in info["scenarios"]:
|
|
227
|
+
dependency_map["files"][scenario] = info["endpoints"]
|
|
228
|
+
|
|
229
|
+
return dependency_map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wup
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
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
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
8
|
-
Project-URL: Repository, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/semcod/wup
|
|
8
|
+
Project-URL: Repository, https://github.com/semcod/wup
|
|
9
9
|
Keywords: wup,watcher,testing,regression,file-monitoring
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
@@ -28,17 +28,17 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
## AI Cost Tracking
|
|
30
30
|
|
|
31
|
-
    
|
|
32
|
+
  
|
|
33
33
|
|
|
34
|
-
- 🤖 **LLM usage:** $
|
|
34
|
+
- 🤖 **LLM usage:** $1.0500 (7 commits)
|
|
35
35
|
- 👤 **Human dev:** ~$200 (2.0h @ $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
|
|
|
@@ -56,6 +56,9 @@ WUP monitors file changes and runs intelligent regression tests using a 3-layer
|
|
|
56
56
|
- 🔍 **Dependency Mapping**: Automatic detection of files → endpoints → services
|
|
57
57
|
- 🚀 **Framework Support**: FastAPI, Flask, Django, Express.js, and more
|
|
58
58
|
- 📝 **Blame Reports**: Detailed regression reports with file/line/commit info
|
|
59
|
+
- ⚙️ **Configuration System**: Declarative configuration via `wup.yaml` file
|
|
60
|
+
- 🎛️ **Per-Service Settings**: Custom test strategies per service
|
|
61
|
+
- 🧪 **TestQL Integration**: Native support for TestQL scenarios
|
|
59
62
|
|
|
60
63
|
## Installation
|
|
61
64
|
|
|
@@ -70,13 +73,16 @@ pip install -e ".[dev]"
|
|
|
70
73
|
## Quick Start
|
|
71
74
|
|
|
72
75
|
```bash
|
|
73
|
-
# 1.
|
|
76
|
+
# 1. Initialize configuration (optional)
|
|
77
|
+
wup init
|
|
78
|
+
|
|
79
|
+
# 2. Build dependency map (one-time setup)
|
|
74
80
|
wup map-deps ./my-project
|
|
75
81
|
|
|
76
|
-
#
|
|
82
|
+
# 3. Start watching for changes
|
|
77
83
|
wup watch ./my-project
|
|
78
84
|
|
|
79
|
-
#
|
|
85
|
+
# 4. Start with live dashboard
|
|
80
86
|
wup watch ./my-project --dashboard
|
|
81
87
|
```
|
|
82
88
|
|
|
@@ -98,7 +104,7 @@ wup map-deps ./my-project --output my-deps.json
|
|
|
98
104
|
### Watch Project
|
|
99
105
|
|
|
100
106
|
```bash
|
|
101
|
-
# Basic watching
|
|
107
|
+
# Basic watching (uses wup.yaml if present)
|
|
102
108
|
wup watch ./my-project
|
|
103
109
|
|
|
104
110
|
# With custom settings
|
|
@@ -109,6 +115,25 @@ wup watch ./my-project \
|
|
|
109
115
|
|
|
110
116
|
# With live dashboard
|
|
111
117
|
wup watch ./my-project --dashboard
|
|
118
|
+
|
|
119
|
+
# Use specific config file
|
|
120
|
+
wup watch ./my-project --config custom-config.yaml
|
|
121
|
+
|
|
122
|
+
# TestQL mode
|
|
123
|
+
wup watch ./my-project --mode testql
|
|
124
|
+
|
|
125
|
+
# Discover endpoints from TestQL scenarios
|
|
126
|
+
wup testql-endpoints /path/to/scenarios --output testql-deps.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Initialize Configuration
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Generate default wup.yaml configuration
|
|
133
|
+
wup init
|
|
134
|
+
|
|
135
|
+
# Generate with custom output path
|
|
136
|
+
wup init --output .wup.yaml
|
|
112
137
|
```
|
|
113
138
|
|
|
114
139
|
### Check Status
|
|
@@ -127,24 +152,24 @@ wup status --deps my-deps.json
|
|
|
127
152
|
|
|
128
153
|
```
|
|
129
154
|
┌─────────────────────────────────────────────────────────────┐
|
|
130
|
-
│ DETECTION LAYER
|
|
155
|
+
│ DETECTION LAYER │
|
|
131
156
|
│ File watching with watchdog + heuristics │
|
|
132
157
|
│ Skips: .git, __pycache__, node_modules, .venv │
|
|
133
158
|
└──────────────────────┬──────────────────────────────────────┘
|
|
134
159
|
│ File change
|
|
135
160
|
▼
|
|
136
161
|
┌─────────────────────────────────────────────────────────────┐
|
|
137
|
-
│ PRIORITY LAYER
|
|
162
|
+
│ PRIORITY LAYER │
|
|
138
163
|
│ Quick test: 3 endpoints max per service │
|
|
139
|
-
│ Duration: ~1-2 seconds
|
|
140
|
-
│ Result: Pass → Done, Fail → Escalate
|
|
164
|
+
│ Duration: ~1-2 seconds │
|
|
165
|
+
│ Result: Pass → Done, Fail → Escalate │
|
|
141
166
|
└──────────────────────┬──────────────────────────────────────┘
|
|
142
167
|
│ Failure
|
|
143
168
|
▼
|
|
144
169
|
┌─────────────────────────────────────────────────────────────┐
|
|
145
|
-
│ DETAIL LAYER
|
|
170
|
+
│ DETAIL LAYER │
|
|
146
171
|
│ Full test: All endpoints with blame report │
|
|
147
|
-
│ Duration: ~3-5 seconds
|
|
172
|
+
│ Duration: ~3-5 seconds │
|
|
148
173
|
│ Result: Regression report with file/line/commit │
|
|
149
174
|
└─────────────────────────────────────────────────────────────┘
|
|
150
175
|
```
|
|
@@ -160,15 +185,94 @@ Full regression: 15s test → 15% CPU spike
|
|
|
160
185
|
|
|
161
186
|
## Configuration
|
|
162
187
|
|
|
188
|
+
### wup.yaml Configuration File
|
|
189
|
+
|
|
190
|
+
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.
|
|
191
|
+
|
|
192
|
+
Generate a default configuration:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
wup init
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Example `wup.yaml`:
|
|
199
|
+
|
|
200
|
+
```yaml
|
|
201
|
+
project:
|
|
202
|
+
name: "my-project"
|
|
203
|
+
description: "My awesome project"
|
|
204
|
+
|
|
205
|
+
watch:
|
|
206
|
+
# Folders to watch (supports glob patterns)
|
|
207
|
+
paths:
|
|
208
|
+
- "app/**"
|
|
209
|
+
- "src/**"
|
|
210
|
+
- "routes/**"
|
|
211
|
+
- "!tests/**" # exclusion
|
|
212
|
+
- "!node_modules/**"
|
|
213
|
+
|
|
214
|
+
# Global exclude patterns
|
|
215
|
+
exclude_patterns:
|
|
216
|
+
- "*.md"
|
|
217
|
+
- "*.txt"
|
|
218
|
+
- "migrations/**"
|
|
219
|
+
|
|
220
|
+
# File types to watch (empty = watch all files)
|
|
221
|
+
# Only changes to these file extensions will trigger tests
|
|
222
|
+
file_types:
|
|
223
|
+
- ".py"
|
|
224
|
+
- ".ts"
|
|
225
|
+
- ".jsx"
|
|
226
|
+
|
|
227
|
+
services:
|
|
228
|
+
# Service configurations - simplified with auto-detection
|
|
229
|
+
# If paths are empty, WUP auto-detects files by service name
|
|
230
|
+
|
|
231
|
+
- name: "users-shell"
|
|
232
|
+
type: "shell"
|
|
233
|
+
# Auto-detects files containing "users-shell"
|
|
234
|
+
|
|
235
|
+
- name: "users-web"
|
|
236
|
+
type: "web"
|
|
237
|
+
# Auto-detects files containing "users-web"
|
|
238
|
+
# Will detect coincidence with users-shell
|
|
239
|
+
|
|
240
|
+
# Or use explicit paths (old style still works)
|
|
241
|
+
- name: "payments"
|
|
242
|
+
paths:
|
|
243
|
+
- "app/payments/**"
|
|
244
|
+
type: "auto"
|
|
245
|
+
|
|
246
|
+
test_strategy:
|
|
247
|
+
quick:
|
|
248
|
+
debounce_s: 2
|
|
249
|
+
max_queue: 5
|
|
250
|
+
timeout_s: 10
|
|
251
|
+
detail:
|
|
252
|
+
debounce_s: 10
|
|
253
|
+
max_queue: 1
|
|
254
|
+
timeout_s: 30
|
|
255
|
+
|
|
256
|
+
testql:
|
|
257
|
+
# TestQL-specific configuration
|
|
258
|
+
scenario_dir: "scenarios/tests"
|
|
259
|
+
smoke_scenario: "smoke.testql.toon.yaml"
|
|
260
|
+
output_format: "json"
|
|
261
|
+
extra_args:
|
|
262
|
+
- "--timeout 10s"
|
|
263
|
+
```
|
|
264
|
+
|
|
163
265
|
### CLI Options
|
|
164
266
|
|
|
165
267
|
| Option | Default | Description |
|
|
166
268
|
|--------|---------|-------------|
|
|
269
|
+
| `--config` | auto | Path to wup.yaml config file |
|
|
167
270
|
| `--cpu-throttle` | 0.8 | CPU usage threshold (0.0-1.0) |
|
|
168
271
|
| `--debounce` | 2 | Debounce time in seconds |
|
|
169
272
|
| `--cooldown` | 300 | Test cooldown in seconds |
|
|
170
273
|
| `--dashboard` | false | Enable live dashboard |
|
|
171
274
|
| `--deps` | deps.json | Dependency map file path |
|
|
275
|
+
| `--mode` | default | Watcher mode: default or testql |
|
|
172
276
|
|
|
173
277
|
### Environment Variables
|
|
174
278
|
|
|
@@ -213,11 +317,21 @@ async def run_detail_test(self, service: str, endpoints: List[str]) -> Dict:
|
|
|
213
317
|
wup/
|
|
214
318
|
├── wup/
|
|
215
319
|
│ ├── __init__.py # Package exports
|
|
320
|
+
│ ├── config.py # Configuration loader
|
|
321
|
+
│ ├── models/
|
|
322
|
+
│ │ ├── __init__.py # Models package
|
|
323
|
+
│ │ └── config.py # Configuration dataclasses
|
|
216
324
|
│ ├── core.py # WupWatcher implementation
|
|
217
325
|
│ ├── dependency_mapper.py # Dependency mapping logic
|
|
326
|
+
│ ├── testql_watcher.py # TestQL integration
|
|
218
327
|
│ └── cli.py # CLI interface
|
|
219
328
|
├── tests/
|
|
220
329
|
│ └── test_wup.py # Unit tests
|
|
330
|
+
├── docs/
|
|
331
|
+
│ ├── 2.md # Refactoring documentation
|
|
332
|
+
│ ├── 3.md # Configuration plan
|
|
333
|
+
│ └── TESTQL_INTEGRATION.md # TestQL integration docs
|
|
334
|
+
├── wup.yaml.example # Example configuration
|
|
221
335
|
├── pyproject.toml # Package configuration
|
|
222
336
|
└── README.md # This file
|
|
223
337
|
```
|
{wup-0.2.1 → wup-0.2.6}/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
|