knots-test-complexity 1.11.0__py3-none-win_amd64.whl

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.
@@ -0,0 +1,447 @@
1
+ Metadata-Version: 2.4
2
+ Name: knots-test-complexity
3
+ Version: 1.11.0
4
+ Classifier: Development Status :: 5 - Production/Stable
5
+ Classifier: Environment :: Console
6
+ Classifier: Intended Audience :: Developers
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Topic :: Software Development :: Quality Assurance
9
+ Classifier: Topic :: Software Development :: Testing
10
+ Summary: Test quality analyzer — validates unit tests have sufficient complexity and boundary coverage (companion to knots)
11
+ Keywords: testing,complexity,static-analysis,coverage
12
+ Author: Brandon Arrendondo
13
+ License: MIT
14
+ Requires-Python: >=3.8
15
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
16
+ Project-URL: Homepage, https://github.com/brandon-arrendondo/knots
17
+ Project-URL: Repository, https://github.com/brandon-arrendondo/knots
18
+
19
+ # knots-test-complexity
20
+
21
+ A Rust-based test quality analyzer for C projects that validates unit tests have sufficient complexity to thoroughly exercise source code.
22
+
23
+ ## Motivation
24
+
25
+ Traditional code coverage metrics (line, branch, function) can be misleading. A test can achieve 100% branch coverage with simple assertions while missing critical edge cases like:
26
+
27
+ - **Overflow scenarios**: `uint16_t` timer wrapping at 65535
28
+ - **Boundary conditions**: Off-by-one errors at array bounds
29
+ - **State transitions**: Complex state machines with temporal dependencies
30
+ - **Error paths**: Multiple error conditions not all tested
31
+
32
+ This tool enforces that tests have sufficient **cyclomatic complexity** to exercise all logical paths and **boundary value testing** to catch edge cases.
33
+
34
+ ## Philosophy
35
+
36
+ > "A test with lower complexity than its source code is likely not testing all scenarios."
37
+
38
+ ### Example: The Overflow Bug
39
+
40
+ ```c
41
+ // Source: Cyclomatic Complexity = 2
42
+ uint16_t timer_ms = 0;
43
+
44
+ void periodic_1ms() {
45
+ timer_ms++; // Overflows at 65535!
46
+ }
47
+
48
+ bool is_timeout(uint16_t start_ms, uint16_t duration_ms) {
49
+ return (timer_ms - start_ms) >= duration_ms;
50
+ }
51
+ ```
52
+
53
+ **Traditional Coverage** (100% line, 100% branch):
54
+ ```c
55
+ void test_timeout() {
56
+ timer_ms = 0;
57
+ TEST_ASSERT_TRUE(is_timeout(0, 100)); // Happy path
58
+ TEST_ASSERT_FALSE(is_timeout(0, 1)); // Boundary
59
+ }
60
+ // PASSES coverage but MISSES overflow bug!
61
+ ```
62
+
63
+ **Tool Enforcement** - Would detect:
64
+ - Test complexity (2) barely meets source complexity (2)
65
+ - Missing boundary tests: 0, 65535, wrap-around scenarios
66
+ - Missing state variation: timer_ms at different values
67
+
68
+ **Better Tests** (Higher Complexity):
69
+ ```c
70
+ void test_timeout_boundaries() {
71
+ // Boundary: timer at 0
72
+ timer_ms = 0;
73
+ TEST_ASSERT_TRUE(is_timeout(0, 100));
74
+
75
+ // Boundary: timer near max
76
+ timer_ms = 65530;
77
+ TEST_ASSERT_TRUE(is_timeout(65520, 100));
78
+
79
+ // CRITICAL: Overflow scenario
80
+ timer_ms = 5; // Wrapped from 65535
81
+ TEST_ASSERT_TRUE(is_timeout(65530, 100)); // Catches overflow!
82
+
83
+ // Multiple start/duration combinations
84
+ for (int i = 0; i < 5; i++) {
85
+ test_scenario(scenarios[i]);
86
+ }
87
+ }
88
+ // Higher complexity test catches the bug!
89
+ ```
90
+
91
+ ## Features
92
+
93
+ ### Core Metrics
94
+
95
+ 1. **Test-to-Source Complexity Ratio**
96
+ - Aggregate cyclomatic complexity of all test functions
97
+ - Compare to aggregate complexity of source functions
98
+ - Default threshold: 70% (configurable)
99
+
100
+ 2. **Boundary Value Detection**
101
+ - Detects integer types: `uint8_t`, `uint16_t`, `uint32_t`, `int8_t`, etc.
102
+ - Identifies range checks: `if (x > MAX)`, `if (x < MIN)`
103
+ - Counts required boundary tests
104
+ - Validates tests cover: MIN, MIN-1, MAX, MAX+1
105
+
106
+ 3. **State Variable Tracking** (Future Enhancement)
107
+ - Identifies `static`, `volatile`, and global variables
108
+ - Requires multiple test scenarios per state variable
109
+ - Validates state transitions are tested
110
+
111
+ ### Output Modes
112
+
113
+ - **Warning Mode** (default): Reports violations but doesn't fail pre-commit
114
+ - **Error Mode**: Fails pre-commit on violations
115
+ - **Verbose Mode**: Shows detailed per-function complexity breakdown
116
+
117
+ ## Building
118
+
119
+ ```bash
120
+ cargo build --release --workspace
121
+ ```
122
+
123
+ The binary will be at `target/release/knots-test-complexity`
124
+
125
+ ## Installation
126
+
127
+ ### From Source
128
+
129
+ ```bash
130
+ cd knots
131
+ cargo build --release --workspace
132
+ # Binary is at target/release/knots-test-complexity
133
+ ```
134
+
135
+ Add to your PATH or copy to a location in your PATH.
136
+
137
+ ### Pre-Commit Integration
138
+
139
+ Add to your project's `.pre-commit-config.yaml`:
140
+
141
+ ```yaml
142
+ repos:
143
+ - repo: https://github.com/brandon-arrendondo/knots
144
+ rev: v0.3.0 # Use specific version tag
145
+ hooks:
146
+ - id: test-complexity
147
+ args:
148
+ - --threshold=0.70
149
+ - --boundary-threshold=0.80
150
+ - --level=warn
151
+ - --framework=ceedling
152
+ - --test-dir=Test
153
+ ```
154
+
155
+ **Configuration Options:**
156
+
157
+ - `--threshold=0.70`: Minimum test-to-source complexity ratio (default: 0.70 = 70%)
158
+ - `--level=warn`: Enforcement level (`warn` or `error`, default: `warn`)
159
+ - `--no-check-boundaries`: Disable boundary value detection (enabled by default)
160
+ - `--verbose`: Show detailed per-file analysis
161
+
162
+ **Example: Strict Enforcement**
163
+ ```yaml
164
+ args:
165
+ - --threshold=0.80
166
+ - --level=error
167
+ - --verbose
168
+ ```
169
+
170
+ **Example: Warning Only (No Boundaries)**
171
+ ```yaml
172
+ args:
173
+ - --threshold=0.70
174
+ - --level=warn
175
+ - --no-check-boundaries
176
+ ```
177
+
178
+ ## Usage
179
+
180
+ ### Command Line
181
+
182
+ Analyze a test file and its corresponding source:
183
+
184
+ ```bash
185
+ knots-test-complexity Test/test_battery_service.c Core/Src/modules/battery_service/battery_service.c
186
+ ```
187
+
188
+ With verbose output:
189
+
190
+ ```bash
191
+ knots-test-complexity -v Test/test_battery_service.c Core/Src/modules/battery_service/battery_service.c
192
+ ```
193
+
194
+ With custom thresholds:
195
+
196
+ ```bash
197
+ knots-test-complexity \
198
+ --threshold=0.70 \
199
+ --boundary-threshold=0.80 \
200
+ --level=error \
201
+ Test/test_timer.c Core/Src/timer.c
202
+ ```
203
+
204
+ ### Output Example
205
+
206
+ ```
207
+ Analyzing Test Quality: test_battery_service.c
208
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
209
+
210
+ Source File: battery_service.c
211
+ Functions: 15
212
+ Total Cyclomatic Complexity: 87
213
+ Boundary Values Detected: 12
214
+ - uint8_t variables: 4 (boundaries: 0, 255)
215
+ - Range checks: 8 (if (x > MAX), etc.)
216
+
217
+ Test File: test_battery_service.c
218
+ Functions: 58
219
+ Total Cyclomatic Complexity: 74
220
+ Boundary Tests Found: 15
221
+
222
+ Complexity Analysis:
223
+ Test/Source Ratio: 85% ✓ (threshold: 70%)
224
+ Test Complexity: 74
225
+ Source Complexity: 87
226
+ Ratio: 74/87 = 0.85
227
+
228
+ Boundary Analysis:
229
+ Required Boundary Tests: 12
230
+ Found Boundary Tests: 15 ✓
231
+ Coverage: 125%
232
+
233
+ Result: ✓ PASS
234
+
235
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
236
+ ```
237
+
238
+ ### Failure Example
239
+
240
+ ```
241
+ Analyzing Test Quality: test_lin_comm_service.c
242
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
243
+
244
+ Source File: lin_comm_service.c
245
+ Functions: 8
246
+ Total Cyclomatic Complexity: 54
247
+ Boundary Values Detected: 8
248
+
249
+ Test File: test_lin_comm_service.c
250
+ Functions: 12
251
+ Total Cyclomatic Complexity: 28
252
+ Boundary Tests Found: 3
253
+
254
+ Complexity Analysis:
255
+ Test/Source Ratio: 52% ✗ (threshold: 70%)
256
+ Test Complexity: 28
257
+ Source Complexity: 54
258
+ Ratio: 28/54 = 0.52
259
+
260
+ Boundary Analysis:
261
+ Required Boundary Tests: 8
262
+ Found Boundary Tests: 3 ✗
263
+ Missing Boundaries:
264
+ - rxByteCounter: 0, 3, 11, 12 (RX_HEADER_SIZE boundaries)
265
+ - rxFrameId: 0, 0x3F (FRAME_MASK boundary)
266
+
267
+ Recommendations:
268
+ 1. Add tests for edge cases and error paths
269
+ 2. Test boundary conditions: 0, max values, overflow
270
+ 3. Add state transition tests (4 static variables detected)
271
+ 4. Consider parametrized tests or loops in test code
272
+
273
+ Result: ✗ FAIL (--level=error)
274
+
275
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
276
+ ```
277
+
278
+ ## Algorithm Details
279
+
280
+ ### 1. Complexity Ratio Calculation
281
+
282
+ ```
283
+ For each test file:
284
+ 1. Parse test file with tree-sitter-c
285
+ 2. Calculate cyclomatic complexity for all test functions
286
+ - Count: if, while, for, switch, &&, ||, ?:
287
+ - Include test helper functions
288
+ 3. Sum total test complexity
289
+
290
+ 4. Find corresponding source file
291
+ 5. Calculate cyclomatic complexity for all source functions
292
+ 6. Sum total source complexity
293
+
294
+ 7. Calculate ratio = test_complexity / source_complexity
295
+ 8. Compare ratio >= threshold (default 70%)
296
+
297
+ 9. Report: PASS or FAIL with recommendations
298
+ ```
299
+
300
+ ### 2. Boundary Value Detection
301
+
302
+ ```
303
+ For source file:
304
+ 1. Find all integer type declarations
305
+ - uint8_t → boundaries: 0, 255
306
+ - uint16_t → boundaries: 0, 65535
307
+ - int8_t → boundaries: -128, 127
308
+
309
+ 2. Find all range checks
310
+ - if (x > MAX) → test MAX, MAX+1
311
+ - if (x < MIN) → test MIN-1, MIN
312
+ - if (x >= threshold) → test threshold-1, threshold
313
+
314
+ 3. Count required boundary tests
315
+
316
+ For test file:
317
+ 1. Find all numeric literals in assertions
318
+ 2. Match literals to source boundaries
319
+ 3. Count covered boundaries
320
+
321
+ 4. Report: boundary_coverage = found / required
322
+ 5. Warn if coverage < 100%
323
+ ```
324
+
325
+ ### 3. Test Helper Function Handling
326
+
327
+ Test helpers ARE included in complexity calculation:
328
+
329
+ ```c
330
+ // Helper complexity counts!
331
+ void simulate_frame(uint8_t id) {
332
+ setup_mocks();
333
+ if (id == SPECIAL) { // +1 complexity
334
+ special_handling();
335
+ }
336
+ verify_results();
337
+ }
338
+
339
+ // Test also counts
340
+ void test_multiple_frames() {
341
+ for (int i = 0; i < 10; i++) { // +1 complexity
342
+ simulate_frame(i); // Helper complexity included
343
+ }
344
+ }
345
+ // Total test complexity = 2 (loop + helper's if)
346
+ ```
347
+
348
+ This encourages well-structured tests with reusable helpers.
349
+
350
+ ## Integration with knots
351
+
352
+ `knots-test-complexity` complements `knots`:
353
+
354
+ | Tool | Purpose | Applied To | Metric |
355
+ |------|---------|------------|--------|
356
+ | **knots** | Source code quality | Production code (`.c`, `.h`) | McCabe & Cognitive complexity per function |
357
+ | **knots-test-complexity** | Test quality | Test code (`test_*.c`) | Aggregate complexity ratio & boundary coverage |
358
+
359
+ **Example Combined Workflow:**
360
+
361
+ ```yaml
362
+ - repo: local
363
+ hooks:
364
+ # Check source code complexity (per-function limits)
365
+ - id: knots
366
+ name: Code Complexity Check
367
+ entry: hooks/pre-commit-wrapper.sh
368
+ language: script
369
+ files: \.(c|h)$
370
+ exclude: ^Test/
371
+ args: [--mccabe-threshold=15, --cognitive-threshold=15]
372
+
373
+ # Check test quality (aggregate complexity ratio)
374
+ - id: test-complexity
375
+ name: Test Quality Check
376
+ entry: hooks/test-complexity-wrapper.sh
377
+ language: script
378
+ files: ^Test/test_.*\.c$
379
+ args: [--threshold=70, --level=warn]
380
+ ```
381
+
382
+ ### Future: Unified Tool
383
+
384
+ A future enhancement could merge both tools:
385
+
386
+ ```bash
387
+ # Unified complexity tool
388
+ complexity-check --source <file.c> --test <test_file.c>
389
+ --source-mccabe-max=15
390
+ --source-cognitive-max=15
391
+ --test-ratio-min=0.70
392
+ --check-boundaries
393
+ ```
394
+
395
+ ## Dependencies
396
+
397
+ ```toml
398
+ [dependencies]
399
+ tree-sitter = "0.22"
400
+ tree-sitter-c = "0.21"
401
+ anyhow = "1.0"
402
+ clap = { version = "4.5", features = ["derive"] }
403
+ regex = "1.10"
404
+ ```
405
+
406
+ ## Project Structure
407
+
408
+ ```
409
+ knots-test-complexity/
410
+ ├── Cargo.toml # Rust project manifest
411
+ ├── README.md # This file
412
+ ├── src/
413
+ │ ├── main.rs # CLI entry point
414
+ │ ├── analyzer.rs # Test quality analyzer
415
+ │ ├── boundary.rs # Boundary value detector
416
+ │ └── reporter.rs # Output formatting
417
+ └── examples/
418
+ ├── test_timer_good.c # Example: sufficient complexity
419
+ ├── test_timer_bad.c # Example: insufficient complexity
420
+ └── README.md # Example documentation
421
+ ```
422
+
423
+ ## Testing
424
+
425
+ Run the test suite:
426
+
427
+ ```bash
428
+ cargo test
429
+ ```
430
+
431
+ Run with verbose output:
432
+
433
+ ```bash
434
+ cargo test -- --nocapture
435
+ ```
436
+
437
+ ## License
438
+
439
+ MIT License. See LICENSE file.
440
+
441
+ ## See Also
442
+
443
+ - **knots**: Source code complexity analyzer
444
+ - **pmccabe**: Industry-standard McCabe complexity tool (validation reference)
445
+ - **Cognitive Complexity**: [SonarSource specification](https://www.sonarsource.com/resources/cognitive-complexity/)
446
+ - **Mutation Testing**: Alternative approach for test quality (future consideration)
447
+
@@ -0,0 +1,5 @@
1
+ knots_test_complexity-1.11.0.data/scripts/knots-test-complexity.exe,sha256=nkfqNx9_E6JmI1qr0Cq1RSaBM5hENFBViw7URfFpiA8,11164160
2
+ knots_test_complexity-1.11.0.dist-info/METADATA,sha256=RXJcV_eP_6iAWPkhk_VIoyqdFxtB73JQq_1ZGl01U00,12908
3
+ knots_test_complexity-1.11.0.dist-info/WHEEL,sha256=2zDlIYIdD4m4N3p5DVEG3iJhGLdhsBQgdH-FqVkAur8,94
4
+ knots_test_complexity-1.11.0.dist-info/sboms/knots-test-complexity.cyclonedx.json,sha256=2T8Oevq-Nha23xUb0Qco6Zu6pWwqTReCWUwJvuADwhg,57590
5
+ knots_test_complexity-1.11.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.14.1)
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64