Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ option(STRICT_LINT "Enable strict linting" ON)
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
option(BUILD_COVERAGE "Enable code coverage reporting" OFF)
option(BUILD_TESTS "Enable unit tests" ON)

# Add include directories
include_directories(include)
Expand Down Expand Up @@ -130,6 +131,7 @@ if(BUILD_TESTS)
add_cloudsql_test(bloom_filter_tests tests/bloom_filter_test.cpp)
add_cloudsql_test(cloudSQL_tests tests/cloudSQL_tests.cpp)
add_cloudsql_test(server_tests tests/server_tests.cpp)
add_cloudsql_test(config_tests tests/config_tests.cpp)
add_cloudsql_test(statement_tests tests/statement_tests.cpp)
add_cloudsql_test(transaction_manager_tests tests/transaction_manager_tests.cpp)
add_cloudsql_test(lock_manager_tests tests/lock_manager_tests.cpp)
Expand Down Expand Up @@ -165,6 +167,8 @@ if(BUILD_TESTS)
add_custom_target(run-tests
COMMAND ${CMAKE_CTEST_COMMAND}
COMMENT "Running all tests via CTest")
else()
message(STATUS "Unit tests disabled (BUILD_TESTS=OFF)")
endif()

# Benchmarks
Expand Down
215 changes: 84 additions & 131 deletions docs/coverage_report.md
Original file line number Diff line number Diff line change
@@ -1,172 +1,125 @@
# cloudSQL Coverage Report

Generated: 2026-04-30
Test Suite: 37 test targets, all passing

## Summary

| Module | Line Coverage | Branch Coverage |
|--------|--------------|-----------------|
| **catalog/** | 83.7% / 90.9% | 94.2% / 75.0% |
| **common/** | 0.0% - 100.0% | 12.9% - 100.0% |
| **distributed/** | 0.0% - 100.0% | 0.0% - 100.0% |
| **executor/** | 12.2% - 100.0% | 0.0% - 100.0% |
| **network/** | 0.0% - 100.0% | 0.0% - 100.0% |
| **parser/** | 29.2% - 100.0% | 0.0% - 100.0% |
| **recovery/** | 0.0% - 100.0% | 0.0% - 100.0% |
| **storage/** | 0.0% - 100.0% | 0.0% - 100.0% |
| **transaction/** | 0.0% - 100.0% | 37.5% - 100.0% |
Generated: 2026-05-08
Test Suite: 38 test targets, all passing (BUILD_COVERAGE=ON, -fprofile-arcs -ftest-coverage -O0)

## Summary (Line Coverage Only)

| Module | Lines Hit / Total | Line % |
|--------|-------------------|--------|
| **catalog** | 211 / 282 | 74.8% |
| **common** | 219 / 271 | 80.8% |
| **distributed** | 742 / 956 | 77.6% |
| **executor** | 1228 / 1548 | 79.3% |
| **network** | 391 / 450 | 86.9% |
| **parser** | 1146 / 1274 | 90.0% |
| **recovery** | 340 / 355 | 95.8% |
| **storage** | 1624 / 1911 | 84.9% |
| **transaction** | 292 / 300 | 97.3% |

**Overall: 6193 / 7347 lines (84.3%)**

## Detailed File Coverage

### catalog/

| File | Lines | Line % | Branches | Branch % |
|------|-------|--------|----------|----------|
| catalog.hpp | 33 | 90.9% | 8 | 75.0% |
| catalog.cpp | 209 | 83.7% | 242 | 94.2% |
| File | Lines Hit/Total | Line % | Branch Taken/Total | Branch % |
|------|-----------------|--------|--------------------|----------|
| catalog.cpp | 211/282 | 74.8% | 105/217 | 48.4% |

### common/

| File | Lines | Line % | Branches | Branch % |
|------|-------|--------|----------|----------|
| arena_allocator.hpp | 85 | 97.7% | 36 | 94.4% |
| bloom_filter.hpp | 3 | 100.0% | 2 | 100.0% |
| bloom_filter.cpp | 2 | 100.0% | 62 | 12.9% |
| **cluster_manager.hpp** | 15 | **100.0%** | 12 | **100.0%** |
| config.hpp | 9 | 44.4% | 2 | 100.0% |
| config.cpp | 2 | 0.0% | 2 | 100.0% |
| fault_injection.hpp | 43 | 90.7% | 50 | 100.0% |
| value.hpp | 12 | 91.7% | 4 | 100.0% |
| File | Lines Hit/Total | Line % | Branch Taken/Total | Branch % |
|------|-----------------|--------|--------------------|----------|
| config.cpp | 125/125 | 100.0% | 169/265 | 63.8% |
| bloom_filter.cpp | 109/146 | 74.7% | 45/80 | 56.3% |

### distributed/

| File | Lines | Line % | Branches | Branch % |
|------|-------|--------|----------|----------|
| distributed_executor.cpp | 724 | 71.0% | 1260 | 72.1% |
| raft_group.hpp | 70 | 90.0% | 236 | 100.0% |
| raft_group.cpp | 11 | 72.7% | 24 | 41.7% |
| raft_manager.hpp | 15 | 60.0% | 6 | 100.0% |
| raft_manager.cpp | 2 | 100.0% | 2 | 0.0% |
| raft_types.hpp | 11 | 0.0% | 2 | 100.0% |
| shard_manager.hpp | 6 | 100.0% | 2 | 100.0% |
| File | Lines Hit/Total | Line % | Branch Taken/Total | Branch % |
|------|-----------------|--------|--------------------|----------|
| distributed_executor.cpp | 516/724 | 71.3% | 545/1260 | 43.3% |
| raft_group.cpp | 257/278 | 92.5% | 147/228 | 64.5% |
| raft_manager.cpp | 50/51 | 98.0% | 41/72 | 56.9% |

### executor/

| File | Lines | Line % | Branches | Branch % |
|------|-------|--------|----------|----------|
| operator.hpp | 43 | 83.7% | 122 | 100.0% |
| operator.cpp | 737 | 88.5% | 845 | 89.3% |
| query_executor.cpp | 41 | 12.2% | 12 | 0.0% |
| query_executor.hpp | 2 | 100.0% | 2 | 0.0% |
| types.hpp | 137 | 85.4% | 112 | 50.9% |
| vectorized_operator.hpp | 150 | 44.0% | 30 | 40.0% |
| File | Lines Hit/Total | Line % | Branch Taken/Total | Branch % |
|------|-----------------|--------|--------------------|----------|
| operator.cpp | 654/737 | 88.9% | 448/721 | 62.1% |
| query_executor.cpp | 627/859 | 73.0% | 700/1679 | 41.7% |

### network/

| File | Lines | Line % | Branches | Branch % |
|------|-------|--------|----------|----------|
| rpc_client.hpp | 34 | 85.3% | 44 | 100.0% |
| rpc_client.cpp | 5 | 100.0% | 2 | 0.0% |
| rpc_message.hpp | 336 | 99.4% | 240 | 57.9% |
| rpc_server.hpp | 23 | 73.9% | 52 | 100.0% |
| rpc_server.cpp | 1 | 0.0% | 4 | 100.0% |
| server.hpp | 28 | 100.0% | 30 | 100.0% |
| server.cpp | 296 | 60.0% | 298 | 40.0% |
| File | Lines Hit/Total | Line % | Branch Taken/Total | Branch % |
|------|-----------------|--------|--------------------|----------|
| server.cpp | 248/296 | 83.8% | 149/298 | 50.0% |
| rpc_client.cpp | 63/70 | 90.0% | 42/64 | 65.6% |
| rpc_server.cpp | 80/84 | 95.2% | 43/59 | 72.9% |

### parser/

| File | Lines | Line % | Branches | Branch % |
|------|-------|--------|----------|----------|
| expression.hpp | 25 | 100.0% | 68 | 11.8% |
| expression.cpp | 31 | 74.2% | 80 | 77.5% |
| lexer.hpp | 24 | 100.0% | 74 | 13.5% |
| lexer.cpp | 4 | 100.0% | 30 | 53.3% |
| parser.hpp | 4 | 100.0% | 2 | 0.0% |
| parser.cpp | 41 | 97.6% | 148 | 100.0% |
| statement.hpp | 1 | 100.0% | 4 | 100.0% |
| statement.cpp | 13 | 23.1% | 4 | 0.0% |
| token.hpp | 1 | 100.0% | 2 | 100.0% |
| File | Lines Hit/Total | Line % | Branch Taken/Total | Branch % |
|------|-----------------|--------|--------------------|----------|
| expression.cpp | 258/312 | 82.7% | 265/463 | 57.3% |
| lexer.cpp | 211/219 | 96.4% | 181/294 | 61.6% |
| parser.cpp | 529/611 | 86.6% | 676/1174 | 57.6% |
| statement.cpp | 124/132 | 93.9% | 127/225 | 56.4% |

### recovery/

| File | Lines | Line % | Branches | Branch % |
|------|-------|--------|----------|----------|
| log_manager.hpp | 24 | 29.2% | 58 | 20.7% |
| log_manager.cpp | 8 | 37.5% | 38 | 5.3% |
| log_record.hpp | 3 | 100.0% | 2 | 100.0% |
| log_record.cpp | 80 | 5.0% | 22 | 0.0% |
| recovery_manager.hpp | 17 | 35.3% | 12 | 33.3% |
| recovery_manager.cpp | 4 | 0.0% | 2 | 0.0% |
| File | Lines Hit/Total | Line % | Branch Taken/Total | Branch % |
|------|-----------------|--------|--------------------|----------|
| log_manager.cpp | 63/70 | 90.0% | 28/50 | 56.0% |
| log_record.cpp | 256/266 | 96.2% | 133/173 | 76.9% |
| recovery_manager.cpp | 23/23 | 100.0% | 0/24 | 0.0% |

### storage/

| File | Lines | Line % | Branches | Branch % |
|------|-------|--------|----------|----------|
| btree_index.hpp | 2 | 100.0% | 2 | 100.0% |
| btree_index.cpp | 25 | 4.0% | 2 | 0.0% |
| buffer_pool_manager.hpp | 1 | 100.0% | 2 | 100.0% |
| buffer_pool_manager.cpp | 32 | 100.0% | 22 | 100.0% |
| columnar_table.hpp | 54 | 100.0% | 14 | 100.0% |
| columnar_table.cpp | 26 | 73.1% | 28 | 92.9% |
| heap_table.hpp | 3 | 100.0% | 2 | 100.0% |
| heap_table.cpp | 142 | 18.3% | 38 | 26.3% |
| lru_replacer.hpp | 12 | 83.3% | 2 | 100.0% |
| lru_replacer.cpp | 1 | 100.0% | 2 | 100.0% |
| page.hpp | 195 | 82.6% | 126 | 96.8% |
| storage_manager.hpp | 8 | 0.0% | 2 | 100.0% |
| storage_manager.cpp | 32 | 96.9% | 32 | 56.2% |
| File | Lines Hit/Total | Line % | Branch Taken/Total | Branch % |
|------|-----------------|--------|--------------------|----------|
| btree_index.cpp | 132/145 | 91.0% | 84/150 | 56.0% |
| buffer_pool_manager.cpp | 175/187 | 93.5% | 122/227 | 53.7% |
| columnar_table.cpp | 124/135 | 91.9% | 152/308 | 49.4% |
| heap_table.cpp | 528/595 | 88.7% | 289/476 | 60.7% |
| lru_replacer.cpp | 44/46 | 95.7% | 24/40 | 60.0% |
| storage_manager.cpp | 106/120 | 88.3% | 51/86 | 59.3% |

### transaction/

| File | Lines | Line % | Branches | Branch % |
|------|-------|--------|----------|----------|
| lock_manager.hpp | 28 | 57.1% | 8 | 50.0% |
| lock_manager.cpp | 86 | 62.8% | 16 | 37.5% |
| transaction.hpp | 1 | 0.0% | 16 | 87.5% |
| transaction_manager.hpp | 1 | 100.0% | 33 | 87.9% |
| transaction_manager.cpp | 68 | 83.8% | 24 | 75.0% |

## Coverage Gaps (Lines < 50%)

### Critical Gaps (< 20% line coverage)
| File | Lines Hit/Total | Line % | Branch Taken/Total | Branch % |
|------|-----------------|--------|--------------------|----------|
| lock_manager.cpp | 80/82 | 97.6% | 78/116 | 67.2% |
| transaction_manager.cpp | 212/218 | 97.2% | 227/386 | 58.8% |

| File | Line % | Issue |
|------|--------|-------|
| storage/btree_index.cpp | 4.0% | Minimal tests |
| recovery/log_record.cpp | 5.0% | Minimal tests |
| executor/query_executor.cpp | 12.2% | Minimal tests |
| storage/heap_table.cpp | 18.3% | Needs more tests |
## Coverage Gaps

### Moderate Gaps (20-50% line coverage)
### Lowest Line Coverage

| File | Line % | Issue |
|------|--------|-------|
| parser/statement.cpp | 23.1% | Partial coverage |
| recovery/log_manager.hpp | 29.2% | Partial coverage |
| recovery/recovery_manager.hpp | 35.3% | Partial coverage |
| recovery/log_manager.cpp | 37.5% | Partial coverage |
| network/server.cpp | 55.7% | Partial coverage |
| transaction/lock_manager.hpp | 57.1% | Partial coverage |

## Branch Coverage Highlights

### Best Branch Coverage (100% lines hit)
- common/cluster_manager.hpp: 100% lines, 100% branches
- common/bloom_filter.hpp: 100% lines, 100% branches
- common/fault_injection.hpp: 90.7% lines, 100% branches
- distributed/shard_manager.hpp: 100% lines, 100% branches
- storage/buffer_pool_manager.cpp: 100% lines, 100% branches
| distributed_executor.cpp | 71.0% | Shard routing and broadcast paths |
| query_executor.cpp | 73.0% | Distributed execution paths |
Comment on lines +101 to +102
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent coverage percentage for distributed_executor.cpp.

Line 101 reports distributed_executor.cpp | 71.0% in the "Lowest Line Coverage" section, but line 41 shows distributed_executor.cpp | 516/724 | 71.3% in the detailed coverage table. The percentages should match.

Since 516/724 = 71.27% ≈ 71.3%, the detailed table appears correct. Update line 101 to show 71.3% for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/coverage_report.md` around lines 101 - 102, The "Lowest Line Coverage"
summary entry for distributed_executor.cpp is inconsistent; change its reported
percentage from 71.0% to 71.3% so it matches the detailed table (516/724 ->
71.3%) — update the summary row for distributed_executor.cpp in the coverage
report to read "71.3%" instead of "71.0%".


### Lowest Branch Coverage
- network/rpc_message.hpp: 29.2% lines, 15.0% branches
- recovery/log_manager.cpp: 37.5% lines, 5.3% branches
- storage/heap_table.cpp: 18.3% lines, 26.3% branches
- parser/expression.hpp: 100.0% lines, 11.8% branches

| File | Branch % | Issue |
|------|----------|-------|
| query_executor.cpp | 41.7% | Executor dispatch branches |
| distributed_executor.cpp | 43.1% | Distributed coordination branches |
| lexer.cpp | 61.6% | Lexer token recognition branches |
| catalog.cpp | 48.4% | Catalog metadata paths |

## Recommendations for Next Tests

1. **shard_manager.hpp** - Already 100% coverage from existing distributed_executor_tests
2. **config.hpp** - 44.4% lines, needs dedicated config_tests.cpp
3. **arena_allocator.hpp** - 97.7% lines, only 3% missing - could add corner cases
4. **heap_table.cpp** - 18.3% lines - needs more tests (but may be covered by logic tests)
1. **query_executor.cpp** — 73.0% line / 41.7% branch coverage. Add tests for:
- Distributed execution paths
- More executor dispatch branches

2. **catalog.cpp** — 74.8% line / 48.4% branch coverage. Add tests for:
- Catalog metadata paths
- Index creation edge cases

3. **distributed_executor.cpp** — 71.3% line / 43.3% branch coverage (improved). Add tests for:
- Insert RPC reply success=false error path
- 2PC coordination failure branches
9 changes: 8 additions & 1 deletion src/storage/btree_index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,16 @@ bool BTreeIndex::Iterator::next(Entry& out_entry) {
std::string slot_str;
if (std::getline(ss, type_str, '|') && std::getline(ss, lexeme, '|') &&
std::getline(ss, page_str, '|') && std::getline(ss, slot_str, '|')) {
int type_id = std::stoi(type_str);
common::Value val;
if (std::stoi(type_str) == static_cast<int>(common::ValueType::TYPE_INT64)) {
if (type_id == static_cast<int>(common::ValueType::TYPE_INT64)) {
val = common::Value::make_int64(std::stoll(lexeme));
} else if (type_id == static_cast<int>(common::ValueType::TYPE_INT32)) {
val = common::Value(static_cast<int32_t>(std::stol(lexeme)));
} else if (type_id == static_cast<int>(common::ValueType::TYPE_INT16)) {
val = common::Value(static_cast<int16_t>(std::stoi(lexeme)));
} else if (type_id == static_cast<int>(common::ValueType::TYPE_INT8)) {
val = common::Value(static_cast<int8_t>(std::stoi(lexeme)));
} else {
Comment on lines +84 to 94
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cd /repo 2>/dev/null || cd . && wc -l src/storage/btree_index.cpp

Repository: poyrazK/cloudSQL

Length of output: 92


🏁 Script executed:

cat -n src/storage/btree_index.cpp | sed -n '75,105p'

Repository: poyrazK/cloudSQL

Length of output: 1722


🏁 Script executed:

# Search for the full context of the deserialization function
rg -A 30 "int type_id = std::stoi" src/storage/btree_index.cpp

Repository: poyrazK/cloudSQL

Length of output: 1275


🏁 Script executed:

# Check if there's a limits header already included
rg "#include.*limits" src/storage/btree_index.cpp

Repository: poyrazK/cloudSQL

Length of output: 42


🏁 Script executed:

# Check the Value class constructor signatures to understand what's accepted
rg -A 5 "class Value" src/common/value.h 2>/dev/null || rg -A 5 "struct Value" --type cpp | head -30

Repository: poyrazK/cloudSQL

Length of output: 42


🏁 Script executed:

# Check if numeric_limits is used anywhere in the codebase
rg "numeric_limits" --type cpp | head -10

Repository: poyrazK/cloudSQL

Length of output: 42


🏁 Script executed:

# Check the full context of the next() function to understand error handling
cat -n src/storage/btree_index.cpp | sed -n '50,110p'

Repository: poyrazK/cloudSQL

Length of output: 2905


🏁 Script executed:

# Search for other std::stol/stoi/stoll conversions in this file to see if there's a pattern
rg "std::stol|std::stoi|std::stoll" src/storage/btree_index.cpp -n

Repository: poyrazK/cloudSQL

Length of output: 603


🏁 Script executed:

# Check if there are tests for this deserialization that might reveal expected behavior
find . -name "*test*btree*" -o -name "*btree*test*" 2>/dev/null | head -5

Repository: poyrazK/cloudSQL

Length of output: 90


🏁 Script executed:

cat -n ./tests/btree_index_tests.cpp | head -80

Repository: poyrazK/cloudSQL

Length of output: 2972


🏁 Script executed:

# Search for test cases that might test boundary conditions
rg -i "boundary|range|overflow|malformed|corrupt" ./tests/btree_index_tests.cpp

Repository: poyrazK/cloudSQL

Length of output: 131


🏁 Script executed:

# Check what happens when std::stoi/std::stol receive out-of-range values
# by checking if there are try-catch blocks in the deserialization function
cat -n src/storage/btree_index.cpp | sed -n '40,115p'

Repository: poyrazK/cloudSQL

Length of output: 3399


Add bounds checks before narrowing integer deserialization

Lines 89, 91, and 93 silently truncate out-of-range values when casting from long/int to int32_t/int16_t/int8_t. For persisted index data, this corrupts reconstructed keys and causes incorrect query results.

Proposed fix
+#include <limits>
...
-            } else if (type_id == static_cast<int>(common::ValueType::TYPE_INT32)) {
-                val = common::Value(static_cast<int32_t>(std::stol(lexeme)));
-            } else if (type_id == static_cast<int>(common::ValueType::TYPE_INT16)) {
-                val = common::Value(static_cast<int16_t>(std::stoi(lexeme)));
-            } else if (type_id == static_cast<int>(common::ValueType::TYPE_INT8)) {
-                val = common::Value(static_cast<int8_t>(std::stoi(lexeme)));
+            } else if (type_id == static_cast<int>(common::ValueType::TYPE_INT32)) {
+                const long long raw = std::stoll(lexeme);
+                if (raw < std::numeric_limits<int32_t>::min() ||
+                    raw > std::numeric_limits<int32_t>::max()) {
+                    eof_ = true;
+                    return false;
+                }
+                val = common::Value(static_cast<int32_t>(raw));
+            } else if (type_id == static_cast<int>(common::ValueType::TYPE_INT16)) {
+                const long long raw = std::stoll(lexeme);
+                if (raw < std::numeric_limits<int16_t>::min() ||
+                    raw > std::numeric_limits<int16_t>::max()) {
+                    eof_ = true;
+                    return false;
+                }
+                val = common::Value(static_cast<int16_t>(raw));
+            } else if (type_id == static_cast<int>(common::ValueType::TYPE_INT8)) {
+                const long long raw = std::stoll(lexeme);
+                if (raw < std::numeric_limits<int8_t>::min() ||
+                    raw > std::numeric_limits<int8_t>::max()) {
+                    eof_ = true;
+                    return false;
+                }
+                val = common::Value(static_cast<int8_t>(raw));
             } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/storage/btree_index.cpp` around lines 84 - 94, The integer
deserialization code in btree_index.cpp currently narrows values for
TYPE_INT32/TYPE_INT16/TYPE_INT8 by static_cast after std::stol/std::stoi which
can silently truncate; update the branch handling for type_id (the checks
comparing to common::ValueType::TYPE_INT32, TYPE_INT16, TYPE_INT8) to parse into
a wider type (e.g., int64_t or long) then validate the parsed value is within
numeric_limits for the target type (int32_t/int16_t/int8_t) before casting; if
out of range, return or signal an error (throw/log) rather than truncating so
reconstructed keys are not corrupted, and keep assignments to val via
common::Value(...) after the safe check using the same lexeme parsing logic.

val = common::Value::make_text(lexeme);
}
Expand Down
Loading
Loading