red64-cli 0.3.0 → 0.6.0
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.
- package/README.md +194 -338
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -13
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/types.d.ts +0 -2
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/HelpScreen.d.ts.map +1 -1
- package/dist/components/screens/HelpScreen.js +0 -2
- package/dist/components/screens/HelpScreen.js.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +5 -8
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +29 -8
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/components/screens/StatusScreen.d.ts.map +1 -1
- package/dist/components/screens/StatusScreen.js +16 -1
- package/dist/components/screens/StatusScreen.js.map +1 -1
- package/dist/services/AgentInvoker.d.ts.map +1 -1
- package/dist/services/AgentInvoker.js +76 -37
- package/dist/services/AgentInvoker.js.map +1 -1
- package/dist/services/ClaudeErrorDetector.d.ts +1 -1
- package/dist/services/ClaudeErrorDetector.d.ts.map +1 -1
- package/dist/services/ClaudeErrorDetector.js +1 -0
- package/dist/services/ClaudeErrorDetector.js.map +1 -1
- package/dist/services/ClaudeHealthCheck.d.ts +7 -0
- package/dist/services/ClaudeHealthCheck.d.ts.map +1 -1
- package/dist/services/ClaudeHealthCheck.js +76 -12
- package/dist/services/ClaudeHealthCheck.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/DockerRunner.js +1 -1
- package/dist/services/DockerRunner.js.map +1 -1
- package/dist/services/PhaseExecutor.d.ts.map +1 -1
- package/dist/services/PhaseExecutor.js +2 -1
- package/dist/services/PhaseExecutor.js.map +1 -1
- package/dist/services/TaskRunner.d.ts.map +1 -1
- package/dist/services/TaskRunner.js +2 -1
- package/dist/services/TaskRunner.js.map +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/stacks/c/code-quality.md +326 -0
- package/framework/stacks/c/coding-style.md +347 -0
- package/framework/stacks/c/conventions.md +513 -0
- package/framework/stacks/c/error-handling.md +350 -0
- package/framework/stacks/c/feedback.md +158 -0
- package/framework/stacks/c/memory-safety.md +408 -0
- package/framework/stacks/c/tech.md +122 -0
- package/framework/stacks/c/testing.md +472 -0
- package/framework/stacks/cpp/code-quality.md +282 -0
- package/framework/stacks/cpp/coding-style.md +363 -0
- package/framework/stacks/cpp/conventions.md +420 -0
- package/framework/stacks/cpp/error-handling.md +264 -0
- package/framework/stacks/cpp/feedback.md +104 -0
- package/framework/stacks/cpp/memory-safety.md +351 -0
- package/framework/stacks/cpp/tech.md +160 -0
- package/framework/stacks/cpp/testing.md +323 -0
- package/framework/stacks/java/code-quality.md +357 -0
- package/framework/stacks/java/coding-style.md +400 -0
- package/framework/stacks/java/conventions.md +437 -0
- package/framework/stacks/java/error-handling.md +408 -0
- package/framework/stacks/java/feedback.md +180 -0
- package/framework/stacks/java/tech.md +126 -0
- package/framework/stacks/java/testing.md +485 -0
- package/framework/stacks/javascript/async-patterns.md +216 -0
- package/framework/stacks/javascript/code-quality.md +182 -0
- package/framework/stacks/javascript/coding-style.md +293 -0
- package/framework/stacks/javascript/conventions.md +268 -0
- package/framework/stacks/javascript/error-handling.md +216 -0
- package/framework/stacks/javascript/feedback.md +80 -0
- package/framework/stacks/javascript/tech.md +114 -0
- package/framework/stacks/javascript/testing.md +209 -0
- package/framework/stacks/loco/code-quality.md +156 -0
- package/framework/stacks/loco/coding-style.md +247 -0
- package/framework/stacks/loco/error-handling.md +225 -0
- package/framework/stacks/loco/feedback.md +35 -0
- package/framework/stacks/loco/loco.md +342 -0
- package/framework/stacks/loco/structure.md +193 -0
- package/framework/stacks/loco/tech.md +129 -0
- package/framework/stacks/loco/testing.md +211 -0
- package/framework/stacks/rust/code-quality.md +370 -0
- package/framework/stacks/rust/coding-style.md +475 -0
- package/framework/stacks/rust/conventions.md +430 -0
- package/framework/stacks/rust/error-handling.md +399 -0
- package/framework/stacks/rust/feedback.md +152 -0
- package/framework/stacks/rust/memory-safety.md +398 -0
- package/framework/stacks/rust/tech.md +121 -0
- package/framework/stacks/rust/testing.md +528 -0
- package/package.json +14 -2
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
# Testing Patterns
|
|
2
|
+
|
|
3
|
+
Comprehensive testing patterns for C projects with Unity (primary) and CMocka (alternative) test frameworks.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
- **Fast feedback**: Unit tests run in milliseconds, no I/O
|
|
10
|
+
- **Arrange-Act-Assert**: Every test follows the same three-step structure
|
|
11
|
+
- **Test behavior, not implementation**: Tests should survive refactoring
|
|
12
|
+
- **Sanitizers always on**: Run tests under ASan + UBSan in CI
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Test Organization
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
tests/
|
|
20
|
+
CMakeLists.txt
|
|
21
|
+
unit/
|
|
22
|
+
test_user.c
|
|
23
|
+
test_config.c
|
|
24
|
+
test_parser.c
|
|
25
|
+
integration/
|
|
26
|
+
test_database.c
|
|
27
|
+
test_http_client.c
|
|
28
|
+
fuzz/
|
|
29
|
+
fuzz_parser.c
|
|
30
|
+
fixtures/
|
|
31
|
+
sample_config.json
|
|
32
|
+
test_data.bin
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Pattern**: Mirror `src/` structure. Prefix all test files with `test_`. Register all tests in CTest.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Unity Test Framework (Primary)
|
|
40
|
+
|
|
41
|
+
### Basic Test Structure
|
|
42
|
+
|
|
43
|
+
```c
|
|
44
|
+
#include "unity.h"
|
|
45
|
+
#include "myproject/math_utils.h"
|
|
46
|
+
|
|
47
|
+
void setUp(void) {
|
|
48
|
+
/* Called before each test -- allocate shared resources */
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void tearDown(void) {
|
|
52
|
+
/* Called after each test -- free shared resources */
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void test_add_positive_numbers(void) {
|
|
56
|
+
/* Arrange & Act */
|
|
57
|
+
int result = add(2, 3);
|
|
58
|
+
|
|
59
|
+
/* Assert */
|
|
60
|
+
TEST_ASSERT_EQUAL_INT(5, result);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
void test_add_negative_numbers(void) {
|
|
64
|
+
TEST_ASSERT_EQUAL_INT(-3, add(-1, -2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
void test_add_overflow_returns_error(void) {
|
|
68
|
+
int result;
|
|
69
|
+
int rc = safe_add(INT_MAX, 1, &result);
|
|
70
|
+
|
|
71
|
+
TEST_ASSERT_EQUAL_INT(ERR_OVERFLOW, rc);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
int main(void) {
|
|
75
|
+
UNITY_BEGIN();
|
|
76
|
+
RUN_TEST(test_add_positive_numbers);
|
|
77
|
+
RUN_TEST(test_add_negative_numbers);
|
|
78
|
+
RUN_TEST(test_add_overflow_returns_error);
|
|
79
|
+
return UNITY_END();
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Common TEST_ASSERT Macros
|
|
84
|
+
|
|
85
|
+
| Macro | Purpose |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `TEST_ASSERT_EQUAL_INT(expected, actual)` | Compare integers |
|
|
88
|
+
| `TEST_ASSERT_EQUAL_STRING(expected, actual)` | Compare null-terminated strings |
|
|
89
|
+
| `TEST_ASSERT_EQUAL_FLOAT(expected, actual, delta)` | Compare floats within tolerance |
|
|
90
|
+
| `TEST_ASSERT_EQUAL_PTR(expected, actual)` | Compare pointers |
|
|
91
|
+
| `TEST_ASSERT_EQUAL_MEMORY(expected, actual, len)` | Compare raw memory |
|
|
92
|
+
| `TEST_ASSERT_NULL(ptr)` | Assert pointer is NULL |
|
|
93
|
+
| `TEST_ASSERT_NOT_NULL(ptr)` | Assert pointer is not NULL |
|
|
94
|
+
| `TEST_ASSERT_TRUE(condition)` | Assert boolean true |
|
|
95
|
+
| `TEST_ASSERT_FALSE(condition)` | Assert boolean false |
|
|
96
|
+
| `TEST_ASSERT_EQUAL_INT_ARRAY(exp, act, len)` | Compare integer arrays |
|
|
97
|
+
|
|
98
|
+
### setUp / tearDown with Resources
|
|
99
|
+
|
|
100
|
+
```c
|
|
101
|
+
static db_t *test_db = NULL;
|
|
102
|
+
static user_repo_t *test_repo = NULL;
|
|
103
|
+
|
|
104
|
+
void setUp(void) {
|
|
105
|
+
test_db = db_open(":memory:");
|
|
106
|
+
TEST_ASSERT_NOT_NULL(test_db);
|
|
107
|
+
db_migrate(test_db);
|
|
108
|
+
test_repo = user_repo_create(test_db);
|
|
109
|
+
TEST_ASSERT_NOT_NULL(test_repo);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
void tearDown(void) {
|
|
113
|
+
user_repo_destroy(test_repo);
|
|
114
|
+
test_repo = NULL;
|
|
115
|
+
db_close(test_db);
|
|
116
|
+
test_db = NULL;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void test_create_user_success(void) {
|
|
120
|
+
/* Arrange */
|
|
121
|
+
create_request_t req = {.name = "Alice", .email = "alice@example.com"};
|
|
122
|
+
user_t user = {0};
|
|
123
|
+
|
|
124
|
+
/* Act */
|
|
125
|
+
int rc = user_repo_create_user(test_repo, &req, &user);
|
|
126
|
+
|
|
127
|
+
/* Assert */
|
|
128
|
+
TEST_ASSERT_EQUAL_INT(ERR_OK, rc);
|
|
129
|
+
TEST_ASSERT_EQUAL_STRING("Alice", user.name);
|
|
130
|
+
TEST_ASSERT_TRUE(user.id > 0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
void test_create_user_duplicate_email(void) {
|
|
134
|
+
create_request_t req = {.name = "Alice", .email = "alice@example.com"};
|
|
135
|
+
user_t user = {0};
|
|
136
|
+
|
|
137
|
+
user_repo_create_user(test_repo, &req, &user); /* First create */
|
|
138
|
+
int rc = user_repo_create_user(test_repo, &req, &user); /* Duplicate */
|
|
139
|
+
|
|
140
|
+
TEST_ASSERT_EQUAL_INT(ERR_ALREADY_EXISTS, rc);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## CMocka Alternative
|
|
147
|
+
|
|
148
|
+
### Basic CMocka Test
|
|
149
|
+
|
|
150
|
+
```c
|
|
151
|
+
#include <stdarg.h>
|
|
152
|
+
#include <stddef.h>
|
|
153
|
+
#include <setjmp.h>
|
|
154
|
+
#include <cmocka.h>
|
|
155
|
+
|
|
156
|
+
#include "myproject/config.h"
|
|
157
|
+
|
|
158
|
+
static void test_config_load_valid(void **state) {
|
|
159
|
+
(void)state;
|
|
160
|
+
|
|
161
|
+
config_t cfg = {0};
|
|
162
|
+
int rc = config_load("fixtures/valid.json", &cfg);
|
|
163
|
+
|
|
164
|
+
assert_int_equal(ERR_OK, rc);
|
|
165
|
+
assert_string_equal("localhost", cfg.host);
|
|
166
|
+
assert_int_equal(8080, cfg.port);
|
|
167
|
+
|
|
168
|
+
config_free(&cfg);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static void test_config_load_missing_file(void **state) {
|
|
172
|
+
(void)state;
|
|
173
|
+
|
|
174
|
+
config_t cfg = {0};
|
|
175
|
+
int rc = config_load("nonexistent.json", &cfg);
|
|
176
|
+
|
|
177
|
+
assert_int_equal(ERR_IO, rc);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
int main(void) {
|
|
181
|
+
const struct CMUnitTest tests[] = {
|
|
182
|
+
cmocka_unit_test(test_config_load_valid),
|
|
183
|
+
cmocka_unit_test(test_config_load_missing_file),
|
|
184
|
+
};
|
|
185
|
+
return cmocka_run_group_tests(tests, NULL, NULL);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### CMocka with Setup/Teardown
|
|
190
|
+
|
|
191
|
+
```c
|
|
192
|
+
static int setup_db(void **state) {
|
|
193
|
+
db_t *db = db_open(":memory:");
|
|
194
|
+
if (!db) return -1;
|
|
195
|
+
db_migrate(db);
|
|
196
|
+
*state = db;
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static int teardown_db(void **state) {
|
|
201
|
+
db_close((db_t *)*state);
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static void test_insert_record(void **state) {
|
|
206
|
+
db_t *db = (db_t *)*state;
|
|
207
|
+
int rc = db_insert(db, "test_key", "test_value");
|
|
208
|
+
assert_int_equal(ERR_OK, rc);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
int main(void) {
|
|
212
|
+
const struct CMUnitTest tests[] = {
|
|
213
|
+
cmocka_unit_test_setup_teardown(test_insert_record, setup_db, teardown_db),
|
|
214
|
+
};
|
|
215
|
+
return cmocka_run_group_tests(tests, NULL, NULL);
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Mocking with Function Pointers
|
|
222
|
+
|
|
223
|
+
In C, dependency injection is achieved via function pointers or vtable-like structs.
|
|
224
|
+
|
|
225
|
+
```c
|
|
226
|
+
/* GOOD: Define an interface via function pointer struct */
|
|
227
|
+
typedef struct {
|
|
228
|
+
int (*find_by_email)(void *ctx, const char *email, user_t *out);
|
|
229
|
+
int (*save)(void *ctx, const user_t *user);
|
|
230
|
+
void *ctx;
|
|
231
|
+
} user_repo_iface_t;
|
|
232
|
+
|
|
233
|
+
/* Production implementation */
|
|
234
|
+
static int pg_find_by_email(void *ctx, const char *email, user_t *out) {
|
|
235
|
+
pg_conn_t *conn = (pg_conn_t *)ctx;
|
|
236
|
+
/* ... real database query ... */
|
|
237
|
+
return ERR_OK;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* Test mock implementation */
|
|
241
|
+
static user_t mock_user;
|
|
242
|
+
static int mock_save_called = 0;
|
|
243
|
+
|
|
244
|
+
static int mock_find_by_email(void *ctx, const char *email, user_t *out) {
|
|
245
|
+
(void)ctx;
|
|
246
|
+
if (strcmp(email, "alice@example.com") == 0) {
|
|
247
|
+
*out = mock_user;
|
|
248
|
+
return ERR_OK;
|
|
249
|
+
}
|
|
250
|
+
return ERR_NOT_FOUND;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
static int mock_save(void *ctx, const user_t *user) {
|
|
254
|
+
(void)ctx;
|
|
255
|
+
mock_save_called = 1;
|
|
256
|
+
mock_user = *user;
|
|
257
|
+
return ERR_OK;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* In test */
|
|
261
|
+
void test_create_user_calls_save(void) {
|
|
262
|
+
mock_save_called = 0;
|
|
263
|
+
user_repo_iface_t repo = {
|
|
264
|
+
.find_by_email = mock_find_by_email,
|
|
265
|
+
.save = mock_save,
|
|
266
|
+
.ctx = NULL,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
create_request_t req = {.name = "Bob", .email = "bob@example.com"};
|
|
270
|
+
user_t result = {0};
|
|
271
|
+
int rc = user_service_create(&repo, &req, &result);
|
|
272
|
+
|
|
273
|
+
TEST_ASSERT_EQUAL_INT(ERR_OK, rc);
|
|
274
|
+
TEST_ASSERT_TRUE(mock_save_called);
|
|
275
|
+
TEST_ASSERT_EQUAL_STRING("Bob", mock_user.name);
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### CMock (Auto-Generated Mocks for Unity)
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# CMock parses headers and generates mock .c/.h files
|
|
283
|
+
ruby vendor/cmock/lib/cmock.rb include/myproject/user_repo.h
|
|
284
|
+
|
|
285
|
+
# Generates:
|
|
286
|
+
# mocks/mock_user_repo.h
|
|
287
|
+
# mocks/mock_user_repo.c
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
```c
|
|
291
|
+
/* Using CMock-generated mocks */
|
|
292
|
+
#include "mock_user_repo.h"
|
|
293
|
+
|
|
294
|
+
void test_service_returns_not_found(void) {
|
|
295
|
+
/* Expect find_by_email to be called and return NOT_FOUND */
|
|
296
|
+
user_repo_find_by_email_ExpectAndReturn("unknown@example.com", NULL, ERR_NOT_FOUND);
|
|
297
|
+
|
|
298
|
+
int rc = user_service_find("unknown@example.com", NULL);
|
|
299
|
+
TEST_ASSERT_EQUAL_INT(ERR_NOT_FOUND, rc);
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Integration Testing
|
|
306
|
+
|
|
307
|
+
```c
|
|
308
|
+
/* Integration tests use real dependencies (database, filesystem) */
|
|
309
|
+
static char test_dir[256];
|
|
310
|
+
|
|
311
|
+
void setUp(void) {
|
|
312
|
+
snprintf(test_dir, sizeof(test_dir), "/tmp/test_%d", getpid());
|
|
313
|
+
mkdir(test_dir, 0755);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
void tearDown(void) {
|
|
317
|
+
/* Clean up test directory */
|
|
318
|
+
char cmd[512];
|
|
319
|
+
snprintf(cmd, sizeof(cmd), "rm -rf %s", test_dir);
|
|
320
|
+
system(cmd);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
void test_config_save_and_load_roundtrip(void) {
|
|
324
|
+
char path[512];
|
|
325
|
+
snprintf(path, sizeof(path), "%s/config.json", test_dir);
|
|
326
|
+
|
|
327
|
+
config_t original = {.host = "localhost", .port = 8080, .debug = true};
|
|
328
|
+
int rc = config_save(&original, path);
|
|
329
|
+
TEST_ASSERT_EQUAL_INT(ERR_OK, rc);
|
|
330
|
+
|
|
331
|
+
config_t loaded = {0};
|
|
332
|
+
rc = config_load(path, &loaded);
|
|
333
|
+
TEST_ASSERT_EQUAL_INT(ERR_OK, rc);
|
|
334
|
+
TEST_ASSERT_EQUAL_STRING("localhost", loaded.host);
|
|
335
|
+
TEST_ASSERT_EQUAL_INT(8080, loaded.port);
|
|
336
|
+
TEST_ASSERT_TRUE(loaded.debug);
|
|
337
|
+
|
|
338
|
+
config_free(&loaded);
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Fuzz Testing with libFuzzer
|
|
345
|
+
|
|
346
|
+
```c
|
|
347
|
+
/* fuzz/fuzz_parser.c */
|
|
348
|
+
#include "myproject/parser.h"
|
|
349
|
+
#include <stdint.h>
|
|
350
|
+
#include <stddef.h>
|
|
351
|
+
|
|
352
|
+
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
|
353
|
+
/* Null-terminate input for string-based parsers */
|
|
354
|
+
char *input = malloc(size + 1);
|
|
355
|
+
if (!input) return 0;
|
|
356
|
+
memcpy(input, data, size);
|
|
357
|
+
input[size] = '\0';
|
|
358
|
+
|
|
359
|
+
result_t result = {0};
|
|
360
|
+
parse(input, &result);
|
|
361
|
+
result_free(&result);
|
|
362
|
+
free(input);
|
|
363
|
+
return 0;
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
```cmake
|
|
368
|
+
add_executable(fuzz_parser fuzz/fuzz_parser.c)
|
|
369
|
+
target_compile_options(fuzz_parser PRIVATE -fsanitize=fuzzer,address,undefined)
|
|
370
|
+
target_link_options(fuzz_parser PRIVATE -fsanitize=fuzzer,address,undefined)
|
|
371
|
+
target_link_libraries(fuzz_parser PRIVATE myapp_lib)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Coverage with gcov / lcov
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
# Build with coverage
|
|
380
|
+
cmake -B build-cov -DCMAKE_BUILD_TYPE=Debug \
|
|
381
|
+
-DCMAKE_C_FLAGS="--coverage"
|
|
382
|
+
cmake --build build-cov
|
|
383
|
+
|
|
384
|
+
# Run tests
|
|
385
|
+
ctest --test-dir build-cov --output-on-failure
|
|
386
|
+
|
|
387
|
+
# Generate HTML report
|
|
388
|
+
lcov --capture --directory build-cov --output-file coverage.info
|
|
389
|
+
lcov --remove coverage.info '/usr/*' '*/test/*' '*/vendor/*' --output-file coverage.info
|
|
390
|
+
genhtml coverage.info --output-directory coverage-report/
|
|
391
|
+
|
|
392
|
+
# Console summary
|
|
393
|
+
gcovr --root . --print-summary
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## CTest Integration
|
|
399
|
+
|
|
400
|
+
```cmake
|
|
401
|
+
# tests/CMakeLists.txt
|
|
402
|
+
enable_testing()
|
|
403
|
+
|
|
404
|
+
# Unity-based tests
|
|
405
|
+
add_executable(test_user tests/unit/test_user.c)
|
|
406
|
+
target_link_libraries(test_user PRIVATE unity myapp_lib)
|
|
407
|
+
add_test(NAME unit_user COMMAND test_user)
|
|
408
|
+
set_tests_properties(unit_user PROPERTIES LABELS "unit")
|
|
409
|
+
|
|
410
|
+
add_executable(test_config tests/unit/test_config.c)
|
|
411
|
+
target_link_libraries(test_config PRIVATE unity myapp_lib)
|
|
412
|
+
add_test(NAME unit_config COMMAND test_config)
|
|
413
|
+
set_tests_properties(unit_config PROPERTIES LABELS "unit")
|
|
414
|
+
|
|
415
|
+
# Integration tests
|
|
416
|
+
add_executable(test_database tests/integration/test_database.c)
|
|
417
|
+
target_link_libraries(test_database PRIVATE unity myapp_lib)
|
|
418
|
+
add_test(NAME integration_database COMMAND test_database)
|
|
419
|
+
set_tests_properties(integration_database PROPERTIES LABELS "integration")
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
# Run by label
|
|
424
|
+
ctest --test-dir build -L unit --output-on-failure
|
|
425
|
+
ctest --test-dir build -L integration --output-on-failure
|
|
426
|
+
|
|
427
|
+
# Run specific test
|
|
428
|
+
ctest --test-dir build -R "unit_user" -V
|
|
429
|
+
|
|
430
|
+
# Run all tests
|
|
431
|
+
ctest --test-dir build --output-on-failure
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Test Commands Summary
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
# Fast feedback
|
|
440
|
+
ctest --test-dir build -L unit --output-on-failure # Unit tests only
|
|
441
|
+
ctest --test-dir build -R "unit_user" -V # Single suite, verbose
|
|
442
|
+
|
|
443
|
+
# Full suite
|
|
444
|
+
ctest --test-dir build --output-on-failure
|
|
445
|
+
|
|
446
|
+
# With sanitizers
|
|
447
|
+
cmake --build build-asan && ctest --test-dir build-asan --output-on-failure
|
|
448
|
+
|
|
449
|
+
# Fuzz testing
|
|
450
|
+
./build/fuzz_parser corpus/ -max_total_time=60
|
|
451
|
+
|
|
452
|
+
# Coverage
|
|
453
|
+
cmake --build build-cov && ctest --test-dir build-cov && gcovr --root . --print-summary
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Anti-Patterns
|
|
459
|
+
|
|
460
|
+
| Anti-Pattern | Problem | Correct Approach |
|
|
461
|
+
|---|---|---|
|
|
462
|
+
| Tests that depend on execution order | Flaky, fragile | Independent setUp/tearDown per test |
|
|
463
|
+
| Testing private/static functions directly | Couples tests to implementation | Test through public API |
|
|
464
|
+
| No tearDown cleanup | Memory leaks obscure real bugs | Free all resources in tearDown |
|
|
465
|
+
| Mocking everything | Tests pass but code is broken | Mock boundaries only (I/O, network, DB) |
|
|
466
|
+
| No sanitizers during test | Memory bugs hide | ASan + UBSan on every CI test run |
|
|
467
|
+
| Giant test functions | Hard to diagnose failures | One assertion per logical behavior |
|
|
468
|
+
| No integration tests | Unit tests pass, system fails | Test real dependencies in integration suite |
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
_Tests document behavior. Each test should read as a specification of what the code does. Name tests after the scenario, not the function._
|