red64-cli 0.5.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 +64 -58
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +2 -2
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/services/AgentInvoker.js +4 -4
- package/dist/services/AgentInvoker.js.map +1 -1
- package/dist/services/ClaudeHealthCheck.d.ts +5 -0
- package/dist/services/ClaudeHealthCheck.d.ts.map +1 -1
- package/dist/services/ClaudeHealthCheck.js +43 -5
- package/dist/services/ClaudeHealthCheck.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/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,323 @@
|
|
|
1
|
+
# Testing Patterns
|
|
2
|
+
|
|
3
|
+
Comprehensive testing patterns for modern C++ projects with Google Test, Google Mock, and Google Benchmark.
|
|
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_service.cpp
|
|
23
|
+
test_payment_processor.cpp
|
|
24
|
+
test_json_parser.cpp
|
|
25
|
+
integration/
|
|
26
|
+
test_database.cpp
|
|
27
|
+
test_http_client.cpp
|
|
28
|
+
fuzz/
|
|
29
|
+
fuzz_json_parser.cpp
|
|
30
|
+
benchmark/
|
|
31
|
+
bench_serialization.cpp
|
|
32
|
+
fixtures/
|
|
33
|
+
test_data.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Pattern**: Mirror `src/` structure. Prefix all test files with `test_`. Register all tests in CTest.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Google Test Basics
|
|
41
|
+
|
|
42
|
+
### TEST -- Simple Tests
|
|
43
|
+
|
|
44
|
+
```cpp
|
|
45
|
+
#include <gtest/gtest.h>
|
|
46
|
+
#include "math_utils.hpp"
|
|
47
|
+
|
|
48
|
+
TEST(MathUtils, AddPositiveNumbers) {
|
|
49
|
+
EXPECT_EQ(add(2, 3), 5);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
TEST(MathUtils, AddNegativeNumbers) {
|
|
53
|
+
EXPECT_EQ(add(-1, -2), -3);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
TEST(MathUtils, AddOverflowReturnsError) {
|
|
57
|
+
auto result = safe_add(INT_MAX, 1);
|
|
58
|
+
ASSERT_FALSE(result.has_value());
|
|
59
|
+
EXPECT_EQ(result.error().code, ErrorCode::kOverflow);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### TEST_F -- Test Fixtures
|
|
64
|
+
|
|
65
|
+
```cpp
|
|
66
|
+
class UserServiceTest : public ::testing::Test {
|
|
67
|
+
protected:
|
|
68
|
+
void SetUp() override {
|
|
69
|
+
repo_ = std::make_shared<MockUserRepo>();
|
|
70
|
+
service_ = std::make_unique<UserService>(repo_);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
std::shared_ptr<MockUserRepo> repo_;
|
|
74
|
+
std::unique_ptr<UserService> service_;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
TEST_F(UserServiceTest, CreateUserSuccess) {
|
|
78
|
+
// Arrange
|
|
79
|
+
EXPECT_CALL(*repo_, find_by_email("alice@example.com"))
|
|
80
|
+
.WillOnce(::testing::Return(std::nullopt));
|
|
81
|
+
EXPECT_CALL(*repo_, save(::testing::_))
|
|
82
|
+
.WillOnce(::testing::Return(User{.id = 1, .name = "Alice"}));
|
|
83
|
+
|
|
84
|
+
// Act
|
|
85
|
+
auto result = service_->create_user({"Alice", "alice@example.com"});
|
|
86
|
+
|
|
87
|
+
// Assert
|
|
88
|
+
ASSERT_TRUE(result.has_value());
|
|
89
|
+
EXPECT_EQ(result->name, "Alice");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
TEST_F(UserServiceTest, CreateUserDuplicateEmailReturnsError) {
|
|
93
|
+
EXPECT_CALL(*repo_, find_by_email("taken@example.com"))
|
|
94
|
+
.WillOnce(::testing::Return(User{.id = 1, .email = "taken@example.com"}));
|
|
95
|
+
|
|
96
|
+
auto result = service_->create_user({"Bob", "taken@example.com"});
|
|
97
|
+
|
|
98
|
+
ASSERT_FALSE(result.has_value());
|
|
99
|
+
EXPECT_EQ(result.error().code, ErrorCode::kAlreadyExists);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### TEST_P -- Parameterized Tests
|
|
104
|
+
|
|
105
|
+
```cpp
|
|
106
|
+
struct ValidationCase {
|
|
107
|
+
std::string input;
|
|
108
|
+
bool expected_valid;
|
|
109
|
+
std::string description;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
class EmailValidationTest : public ::testing::TestWithParam<ValidationCase> {};
|
|
113
|
+
|
|
114
|
+
TEST_P(EmailValidationTest, ValidatesCorrectly) {
|
|
115
|
+
const auto& [input, expected_valid, description] = GetParam();
|
|
116
|
+
EXPECT_EQ(is_valid_email(input), expected_valid) << description;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
INSTANTIATE_TEST_SUITE_P(
|
|
120
|
+
EmailCases,
|
|
121
|
+
EmailValidationTest,
|
|
122
|
+
::testing::Values(
|
|
123
|
+
ValidationCase{"user@example.com", true, "standard email"},
|
|
124
|
+
ValidationCase{"user@sub.example.com", true, "subdomain"},
|
|
125
|
+
ValidationCase{"invalid", false, "no at sign"},
|
|
126
|
+
ValidationCase{"", false, "empty string"},
|
|
127
|
+
ValidationCase{"@example.com", false, "no local part"}
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Google Mock
|
|
135
|
+
|
|
136
|
+
### Defining Mocks
|
|
137
|
+
|
|
138
|
+
```cpp
|
|
139
|
+
#include <gmock/gmock.h>
|
|
140
|
+
|
|
141
|
+
class UserRepo {
|
|
142
|
+
public:
|
|
143
|
+
virtual ~UserRepo() = default;
|
|
144
|
+
virtual std::optional<User> find_by_email(std::string_view email) = 0;
|
|
145
|
+
virtual Result<User> save(const User& user) = 0;
|
|
146
|
+
virtual std::vector<User> find_active() = 0;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
class MockUserRepo : public UserRepo {
|
|
150
|
+
public:
|
|
151
|
+
MOCK_METHOD(std::optional<User>, find_by_email, (std::string_view), (override));
|
|
152
|
+
MOCK_METHOD(Result<User>, save, (const User&), (override));
|
|
153
|
+
MOCK_METHOD(std::vector<User>, find_active, (), (override));
|
|
154
|
+
};
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Matchers and Actions
|
|
158
|
+
|
|
159
|
+
```cpp
|
|
160
|
+
using ::testing::_;
|
|
161
|
+
using ::testing::Return;
|
|
162
|
+
using ::testing::HasSubstr;
|
|
163
|
+
using ::testing::ElementsAre;
|
|
164
|
+
using ::testing::Field;
|
|
165
|
+
|
|
166
|
+
TEST_F(UserServiceTest, SavePassesCorrectData) {
|
|
167
|
+
EXPECT_CALL(*repo_, find_by_email(_)).WillOnce(Return(std::nullopt));
|
|
168
|
+
|
|
169
|
+
// Verify the saved user has the correct name
|
|
170
|
+
EXPECT_CALL(*repo_, save(Field(&User::name, "Alice")))
|
|
171
|
+
.WillOnce(Return(User{.id = 1, .name = "Alice"}));
|
|
172
|
+
|
|
173
|
+
service_->create_user({"Alice", "alice@example.com"});
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Integration Testing
|
|
180
|
+
|
|
181
|
+
```cpp
|
|
182
|
+
class DatabaseIntegrationTest : public ::testing::Test {
|
|
183
|
+
protected:
|
|
184
|
+
void SetUp() override {
|
|
185
|
+
db_ = std::make_unique<Database>(":memory:");
|
|
186
|
+
db_->migrate();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
void TearDown() override {
|
|
190
|
+
db_.reset();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
std::unique_ptr<Database> db_;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
TEST_F(DatabaseIntegrationTest, InsertAndRetrieveUser) {
|
|
197
|
+
auto repo = UserRepo(*db_);
|
|
198
|
+
|
|
199
|
+
auto saved = repo.save(User{.name = "Alice", .email = "alice@example.com"});
|
|
200
|
+
ASSERT_TRUE(saved.has_value());
|
|
201
|
+
|
|
202
|
+
auto found = repo.find_by_email("alice@example.com");
|
|
203
|
+
ASSERT_TRUE(found.has_value());
|
|
204
|
+
EXPECT_EQ(found->name, "Alice");
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Google Benchmark
|
|
211
|
+
|
|
212
|
+
```cpp
|
|
213
|
+
#include <benchmark/benchmark.h>
|
|
214
|
+
#include "serializer.hpp"
|
|
215
|
+
|
|
216
|
+
static void BM_JsonSerialize(benchmark::State& state) {
|
|
217
|
+
auto user = User{.id = 1, .name = "Alice", .email = "alice@example.com"};
|
|
218
|
+
for (auto _ : state) {
|
|
219
|
+
auto json = serialize_to_json(user);
|
|
220
|
+
benchmark::DoNotOptimize(json);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
BENCHMARK(BM_JsonSerialize);
|
|
224
|
+
|
|
225
|
+
static void BM_JsonDeserialize(benchmark::State& state) {
|
|
226
|
+
auto json = R"({"id":1,"name":"Alice","email":"alice@example.com"})";
|
|
227
|
+
for (auto _ : state) {
|
|
228
|
+
auto user = deserialize_from_json<User>(json);
|
|
229
|
+
benchmark::DoNotOptimize(user);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
BENCHMARK(BM_JsonDeserialize);
|
|
233
|
+
|
|
234
|
+
BENCHMARK_MAIN();
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Fuzz Testing with libFuzzer
|
|
240
|
+
|
|
241
|
+
```cpp
|
|
242
|
+
// fuzz/fuzz_json_parser.cpp
|
|
243
|
+
#include "json_parser.hpp"
|
|
244
|
+
#include <cstdint>
|
|
245
|
+
#include <cstddef>
|
|
246
|
+
|
|
247
|
+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|
248
|
+
std::string_view input(reinterpret_cast<const char*>(data), size);
|
|
249
|
+
auto result = parse_json(input);
|
|
250
|
+
// If it does not crash or trigger sanitizers, the input is handled safely
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
```cmake
|
|
256
|
+
# CMakeLists.txt for fuzz targets
|
|
257
|
+
add_executable(fuzz_json_parser fuzz/fuzz_json_parser.cpp)
|
|
258
|
+
target_compile_options(fuzz_json_parser PRIVATE -fsanitize=fuzzer,address,undefined)
|
|
259
|
+
target_link_options(fuzz_json_parser PRIVATE -fsanitize=fuzzer,address,undefined)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Run fuzzer
|
|
264
|
+
./build/fuzz_json_parser corpus/ -max_total_time=60
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## CMake Test Integration
|
|
270
|
+
|
|
271
|
+
```cmake
|
|
272
|
+
# tests/CMakeLists.txt
|
|
273
|
+
include(GoogleTest)
|
|
274
|
+
|
|
275
|
+
add_executable(unit_tests
|
|
276
|
+
unit/test_user_service.cpp
|
|
277
|
+
unit/test_payment_processor.cpp
|
|
278
|
+
)
|
|
279
|
+
target_link_libraries(unit_tests PRIVATE
|
|
280
|
+
GTest::gtest_main
|
|
281
|
+
GTest::gmock
|
|
282
|
+
myapp_lib
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
gtest_discover_tests(unit_tests)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Test Commands
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
# Fast feedback
|
|
294
|
+
ctest --test-dir build -R unit --output-on-failure # Unit tests only
|
|
295
|
+
ctest --test-dir build -R "UserService" -V # Single suite, verbose
|
|
296
|
+
|
|
297
|
+
# Full suite
|
|
298
|
+
ctest --test-dir build --output-on-failure
|
|
299
|
+
|
|
300
|
+
# With sanitizers
|
|
301
|
+
cmake --build build-asan && ctest --test-dir build-asan --output-on-failure
|
|
302
|
+
|
|
303
|
+
# Benchmarks (not in CTest by default)
|
|
304
|
+
./build/bench/bench_serialization --benchmark_format=json
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Test Markers (CTest Labels)
|
|
310
|
+
|
|
311
|
+
```cmake
|
|
312
|
+
set_tests_properties(unit_tests PROPERTIES LABELS "unit")
|
|
313
|
+
set_tests_properties(integration_tests PROPERTIES LABELS "integration")
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
ctest --test-dir build -L unit # Run only unit tests
|
|
318
|
+
ctest --test-dir build -L integration # Run only integration tests
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
_Tests document behavior. Each test should read as a specification of what the code does._
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# Code Quality Standards
|
|
2
|
+
|
|
3
|
+
Project memory for code quality conventions, static analysis, architecture testing, and null safety in Java 21+ with Gradle Kotlin DSL.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
- **Automate everything**: If a rule can be checked by a tool, it should be
|
|
10
|
+
- **Shift left**: Catch bugs at compile time, not in production
|
|
11
|
+
- **Architecture as code**: Enforce package dependencies and layering with ArchUnit
|
|
12
|
+
- **Zero warnings policy**: Treat warnings as errors in CI
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Checkstyle (Google Java Style)
|
|
17
|
+
|
|
18
|
+
### Gradle Configuration
|
|
19
|
+
|
|
20
|
+
```kotlin
|
|
21
|
+
// build.gradle.kts
|
|
22
|
+
plugins {
|
|
23
|
+
id("checkstyle")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
checkstyle {
|
|
27
|
+
toolVersion = "10.20.1"
|
|
28
|
+
configFile = file("config/checkstyle/google_checks.xml")
|
|
29
|
+
isIgnoreFailures = false
|
|
30
|
+
maxWarnings = 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
tasks.withType<Checkstyle> {
|
|
34
|
+
reports {
|
|
35
|
+
xml.required.set(true)
|
|
36
|
+
html.required.set(true)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Key Rules Enforced
|
|
42
|
+
|
|
43
|
+
| Rule | Description |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `Indentation` | 2 spaces (Google style) or 4 spaces (project choice) |
|
|
46
|
+
| `LineLength` | Max 120 characters |
|
|
47
|
+
| `MethodLength` | Max 40 lines |
|
|
48
|
+
| `ParameterNumber` | Max 5 parameters |
|
|
49
|
+
| `AvoidStarImport` | No wildcard imports |
|
|
50
|
+
| `NeedBraces` | Braces required for all control structures |
|
|
51
|
+
| `MissingJavadocMethod` | Javadoc on public methods |
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
./gradlew checkstyleMain checkstyleTest
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## SpotBugs
|
|
60
|
+
|
|
61
|
+
### Gradle Configuration
|
|
62
|
+
|
|
63
|
+
```kotlin
|
|
64
|
+
// build.gradle.kts
|
|
65
|
+
plugins {
|
|
66
|
+
id("com.github.spotbugs") version "6.0.26"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
spotbugs {
|
|
70
|
+
effort.set(com.github.spotbugs.snom.Effort.MAX)
|
|
71
|
+
reportLevel.set(com.github.spotbugs.snom.Confidence.MEDIUM)
|
|
72
|
+
excludeFilter.set(file("config/spotbugs/exclude.xml"))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
tasks.withType<com.github.spotbugs.snom.SpotBugsTask> {
|
|
76
|
+
reports.create("html") { required.set(true) }
|
|
77
|
+
reports.create("xml") { required.set(true) }
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Exclusion Filter
|
|
82
|
+
|
|
83
|
+
```xml
|
|
84
|
+
<!-- config/spotbugs/exclude.xml -->
|
|
85
|
+
<FindBugsFilter>
|
|
86
|
+
<Match>
|
|
87
|
+
<Class name="~.*\.dto\..*" />
|
|
88
|
+
<Bug pattern="EI_EXPOSE_REP,EI_EXPOSE_REP2" />
|
|
89
|
+
</Match>
|
|
90
|
+
<Match>
|
|
91
|
+
<Source name="~.*Test\.java" />
|
|
92
|
+
</Match>
|
|
93
|
+
</FindBugsFilter>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
./gradlew spotbugsMain
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Error Prone
|
|
103
|
+
|
|
104
|
+
Compile-time bug detection from Google:
|
|
105
|
+
|
|
106
|
+
```kotlin
|
|
107
|
+
// build.gradle.kts
|
|
108
|
+
plugins {
|
|
109
|
+
id("net.ltgt.errorprone") version "4.1.0"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
dependencies {
|
|
113
|
+
errorprone("com.google.errorprone:error_prone_core:2.36.0")
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
tasks.withType<JavaCompile> {
|
|
117
|
+
options.errorprone {
|
|
118
|
+
disableWarningsInGeneratedCode.set(true)
|
|
119
|
+
error(
|
|
120
|
+
"MissingOverride",
|
|
121
|
+
"EqualsHashCode",
|
|
122
|
+
"ReturnValueIgnored",
|
|
123
|
+
"UnnecessaryParentheses",
|
|
124
|
+
"FallThrough",
|
|
125
|
+
"MissingCasesInEnumSwitch",
|
|
126
|
+
"UnusedVariable"
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Common Error Prone Catches
|
|
133
|
+
|
|
134
|
+
```java
|
|
135
|
+
// Error Prone catches: MissingOverride
|
|
136
|
+
public class UserService implements Service {
|
|
137
|
+
public void process() { ... } // ERROR: missing @Override
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Error Prone catches: ReturnValueIgnored
|
|
141
|
+
String trimmed = name.trim(); // GOOD
|
|
142
|
+
name.trim(); // ERROR: return value ignored (String is immutable)
|
|
143
|
+
|
|
144
|
+
// Error Prone catches: EqualsHashCode
|
|
145
|
+
public class User {
|
|
146
|
+
@Override
|
|
147
|
+
public boolean equals(Object o) { ... }
|
|
148
|
+
// ERROR: equals() without hashCode()
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## ArchUnit (Architecture Tests)
|
|
155
|
+
|
|
156
|
+
### Dependency
|
|
157
|
+
|
|
158
|
+
```kotlin
|
|
159
|
+
// build.gradle.kts
|
|
160
|
+
dependencies {
|
|
161
|
+
testImplementation("com.tngtech.archunit:archunit-junit5:1.3.0")
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Architecture Rules
|
|
166
|
+
|
|
167
|
+
```java
|
|
168
|
+
@AnalyzeClasses(packages = "com.example")
|
|
169
|
+
class ArchitectureTest {
|
|
170
|
+
|
|
171
|
+
@ArchTest
|
|
172
|
+
static final ArchRule layerDependencies = layeredArchitecture()
|
|
173
|
+
.consideringAllDependencies()
|
|
174
|
+
.layer("Controller").definedBy("..controller..")
|
|
175
|
+
.layer("Service").definedBy("..service..")
|
|
176
|
+
.layer("Repository").definedBy("..repository..")
|
|
177
|
+
.layer("Domain").definedBy("..domain..")
|
|
178
|
+
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
|
|
179
|
+
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller", "Service")
|
|
180
|
+
.whereLayer("Repository").mayOnlyBeAccessedByLayers("Service")
|
|
181
|
+
.whereLayer("Domain").mayOnlyBeAccessedByLayers("Service", "Repository", "Controller");
|
|
182
|
+
|
|
183
|
+
@ArchTest
|
|
184
|
+
static final ArchRule servicesShouldNotDependOnControllers =
|
|
185
|
+
noClasses().that().resideInAPackage("..service..")
|
|
186
|
+
.should().dependOnClassesThat().resideInAPackage("..controller..");
|
|
187
|
+
|
|
188
|
+
@ArchTest
|
|
189
|
+
static final ArchRule controllersShouldBeAnnotated =
|
|
190
|
+
classes().that().resideInAPackage("..controller..")
|
|
191
|
+
.should().beAnnotatedWith(RestController.class);
|
|
192
|
+
|
|
193
|
+
@ArchTest
|
|
194
|
+
static final ArchRule noFieldInjection =
|
|
195
|
+
noFields().should().beAnnotatedWith(Autowired.class)
|
|
196
|
+
.because("Use constructor injection instead of field injection");
|
|
197
|
+
|
|
198
|
+
@ArchTest
|
|
199
|
+
static final ArchRule recordsShouldBeInDtoPackage =
|
|
200
|
+
classes().that().areRecords()
|
|
201
|
+
.and().haveSimpleNameEndingWith("Request")
|
|
202
|
+
.should().resideInAPackage("..dto..");
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## SonarQube Integration
|
|
209
|
+
|
|
210
|
+
### Gradle Plugin
|
|
211
|
+
|
|
212
|
+
```kotlin
|
|
213
|
+
// build.gradle.kts
|
|
214
|
+
plugins {
|
|
215
|
+
id("org.sonarqube") version "5.1.0.4882"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
sonar {
|
|
219
|
+
properties {
|
|
220
|
+
property("sonar.projectKey", "my-project")
|
|
221
|
+
property("sonar.host.url", "http://localhost:9000")
|
|
222
|
+
property("sonar.java.source", "21")
|
|
223
|
+
property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/jacocoTestReport.xml")
|
|
224
|
+
property("sonar.exclusions", "**/dto/**,**/config/**")
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
./gradlew sonar -Dsonar.token=your-token
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Quality Gate Metrics
|
|
234
|
+
|
|
235
|
+
| Metric | Threshold |
|
|
236
|
+
|---|---|
|
|
237
|
+
| Coverage | >= 80% |
|
|
238
|
+
| Duplicated lines | <= 3% |
|
|
239
|
+
| Maintainability rating | A |
|
|
240
|
+
| Reliability rating | A |
|
|
241
|
+
| Security rating | A |
|
|
242
|
+
| Technical debt ratio | <= 5% |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Null Safety
|
|
247
|
+
|
|
248
|
+
### @Nullable / @NonNull Annotations
|
|
249
|
+
|
|
250
|
+
```java
|
|
251
|
+
// Use Spring's or JSpecify annotations project-wide
|
|
252
|
+
import org.springframework.lang.NonNull;
|
|
253
|
+
import org.springframework.lang.Nullable;
|
|
254
|
+
|
|
255
|
+
// GOOD: Annotate boundaries
|
|
256
|
+
public class UserService {
|
|
257
|
+
|
|
258
|
+
@NonNull
|
|
259
|
+
public User getUser(@NonNull Long id) {
|
|
260
|
+
return userRepository.findById(id)
|
|
261
|
+
.orElseThrow(() -> new NotFoundException("User", id));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@Nullable
|
|
265
|
+
public User findByEmail(@NonNull String email) {
|
|
266
|
+
return userRepository.findByEmail(email).orElse(null);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// GOOD: Package-level default with @NonNullApi
|
|
271
|
+
// package-info.java
|
|
272
|
+
@NonNullApi
|
|
273
|
+
package com.example.user.service;
|
|
274
|
+
|
|
275
|
+
import org.springframework.lang.NonNullApi;
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Optional vs @Nullable
|
|
279
|
+
|
|
280
|
+
| Return Type | When to Use |
|
|
281
|
+
|---|---|
|
|
282
|
+
| `Optional<T>` | Public API where absence is a normal outcome |
|
|
283
|
+
| `@Nullable T` | Internal methods, performance-sensitive code |
|
|
284
|
+
| `T` (non-null) | When null is never valid -- throw if missing |
|
|
285
|
+
|
|
286
|
+
```java
|
|
287
|
+
// GOOD: Clear null contract
|
|
288
|
+
public Optional<User> findByEmail(String email) { ... } // public API
|
|
289
|
+
@Nullable User lookupCache(Long id) { ... } // internal
|
|
290
|
+
|
|
291
|
+
// BAD: Ambiguous null contract
|
|
292
|
+
public User findByEmail(String email) { ... } // returns null? throws? who knows
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Spotless (Auto-Formatting)
|
|
298
|
+
|
|
299
|
+
```kotlin
|
|
300
|
+
// build.gradle.kts
|
|
301
|
+
plugins {
|
|
302
|
+
id("com.diffplug.spotless") version "7.0.0"
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
spotless {
|
|
306
|
+
java {
|
|
307
|
+
googleJavaFormat("1.24.0")
|
|
308
|
+
removeUnusedImports()
|
|
309
|
+
trimTrailingWhitespace()
|
|
310
|
+
endWithNewline()
|
|
311
|
+
}
|
|
312
|
+
kotlinGradle {
|
|
313
|
+
ktlint()
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
./gradlew spotlessCheck # Verify formatting
|
|
320
|
+
./gradlew spotlessApply # Auto-format
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## CI Pipeline Quality Commands
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
# Full quality pipeline (run in CI)
|
|
329
|
+
./gradlew clean build # Compile + tests
|
|
330
|
+
./gradlew checkstyleMain # Style enforcement
|
|
331
|
+
./gradlew spotbugsMain # Bug detection
|
|
332
|
+
./gradlew spotlessCheck # Format verification
|
|
333
|
+
./gradlew jacocoTestReport # Coverage report
|
|
334
|
+
./gradlew sonar # SonarQube analysis
|
|
335
|
+
|
|
336
|
+
# Quick local check
|
|
337
|
+
./gradlew check # Runs all verification tasks
|
|
338
|
+
./gradlew spotlessApply && ./gradlew check # Format + verify
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Anti-Patterns
|
|
344
|
+
|
|
345
|
+
| Anti-Pattern | Problem | Correct Approach |
|
|
346
|
+
|---|---|---|
|
|
347
|
+
| No static analysis in CI | Bugs slip through to production | Run checkstyle + spotbugs + errorprone in CI |
|
|
348
|
+
| Suppressing all warnings | Hides real issues | Suppress only with documented justification |
|
|
349
|
+
| No architecture tests | Layering violations accumulate | ArchUnit tests enforce boundaries |
|
|
350
|
+
| Field injection (`@Autowired`) | Hides dependencies, untestable | Constructor injection |
|
|
351
|
+
| Missing null annotations | NPE surprises at runtime | `@NonNullApi` package default + `@Nullable` opt-in |
|
|
352
|
+
| Manual formatting debates | Wastes time in code review | Spotless auto-format, no discussion |
|
|
353
|
+
| Ignoring SonarQube debt | Technical debt grows silently | Quality gate blocks merges |
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
_Quality is not an afterthought. Automate checks, enforce architecture, and treat every warning as a bug waiting to happen._
|