Skip to content

Improve Test Coverage to >85%#15

Open
poyrazK wants to merge 8 commits intomainfrom
feature/improve-coverage
Open

Improve Test Coverage to >85%#15
poyrazK wants to merge 8 commits intomainfrom
feature/improve-coverage

Conversation

@poyrazK
Copy link
Owner

@poyrazK poyrazK commented Mar 15, 2026

This PR implements the test coverage improvement plan by targeting the weakest modules: Catalog, Transaction, and Network/Utils. It adds 3 new test suites and integrates them into the CI pipeline.

Summary by CodeRabbit

  • Tests
    • Added comprehensive unit tests covering transactions (concurrency and deep rollback), catalog behaviors (missing/duplicate entities and replay scenarios), and utilities (value edge-cases, comparisons, hashing, and RPC failure paths).
  • Chores
    • Registered the new test suites so they run alongside existing tests.

@coderabbitai
Copy link

coderabbitai bot commented Mar 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d65e9de7-7e81-438f-9ecd-6964e3af5d28

📥 Commits

Reviewing files that changed from the base of the PR and between 065a609 and 0941745.

📒 Files selected for processing (1)
  • tests/transaction_coverage_tests.cpp

📝 Walkthrough

Walkthrough

Adds three new unit-test suites and registers them in the build: catalog_coverage_tests, transaction_coverage_tests, and utils_coverage_tests; tests exercise Catalog operations, transaction/lock-manager concurrency and rollback, and Value/RpcClient edge cases.

Changes

Cohort / File(s) Summary
Build Configuration
CMakeLists.txt
Under BUILD_TESTS, registers three new test targets via add_cloudsql_test: catalog_coverage_tests, transaction_coverage_tests, and utils_coverage_tests.
Catalog Tests
tests/catalog_coverage_tests.cpp
New tests for Catalog: missing/duplicate entity handling, create/drop via simulated Raft log entries, and no-op Raft entry behavior.
Transaction & Locking Tests
tests/transaction_coverage_tests.cpp
New fixture and tests for Transaction/LockManager: multi-threaded lock contention, blocking/unblocking behavior, deep rollback (INSERT/UPDATE/DELETE undo) and teardown cleanup.
Utility Tests
tests/utils_coverage_tests.cpp
New tests for Value and RpcClient: accessor edge cases, comparisons/hashing/NULL behavior, and RpcClient failure paths (connect/call/send).

Sequence Diagram(s)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I peeked at logs and chased a bug,
Wrote tests that pull, push, and gently tug.
Locks held tight, then loosened free,
Values hashed and RPCs set to flee.
A carrot for coverage — hop, hurray, hug!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Improve Test Coverage to >85%' directly aligns with the main change: adding three new test suites to increase overall test coverage above 85%.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/improve-coverage
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
tests/utils_coverage_tests.cpp (1)

86-96: Test covers connection failure paths well, but consider potential flakiness.

The test uses port 1 which is typically privileged and closed, but network behavior can vary across environments. If CI runs in containers with different network configurations, this test might behave unexpectedly. Consider adding a comment documenting this assumption, or using a mock/stub approach for more deterministic testing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/utils_coverage_tests.cpp` around lines 86 - 96, The test relies on
reaching an unreachable service on localhost (RpcClient constructor,
connect/is_connected/call/send_only) which can be flaky; replace the hardcoded
localhost+port assumption by using a non-routable TEST-NET address (e.g.,
192.0.2.1) or, preferably, refactor to a deterministic stub/mock of RpcClient
and call the same methods (connect, is_connected, call, send_only) to simulate
failure; also add a short comment explaining the chosen approach and why it
avoids CI network variability so the intent is clear.
tests/transaction_coverage_tests.cpp (1)

78-135: Good rollback coverage, but cleanup could be more robust.

The test correctly exercises the undo log paths for INSERT, UPDATE (with old_rid), and DELETE operations, verifying that tm.abort() properly reverses all changes.

Consider using a test fixture with SetUp/TearDown to ensure cleanup runs even if assertions fail:

♻️ Suggested improvement for robust cleanup
+class TransactionCoverageTestFixture : public ::testing::Test {
+protected:
+    void TearDown() override {
+        std::remove("./test_data/rollback_stress.heap");
+        // Consider also cleaning up the directory if needed
+    }
+};

-TEST(TransactionCoverageTests, DeepRollback) {
+TEST_F(TransactionCoverageTestFixture, DeepRollback) {
     // ... test body ...
-    static_cast<void>(std::remove("./test_data/rollback_stress.heap"));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/transaction_coverage_tests.cpp` around lines 78 - 135, Replace the
standalone TEST( TransactionCoverageTests, DeepRollback ) with a test fixture so
cleanup always runs: create a class TransactionCoverageTests : public
::testing::Test and implement SetUp to initialize Catalog::create(),
StorageManager disk_manager, BufferPoolManager bpm, LockManager lm,
TransactionManager tm, and create the HeapTable used by the test; implement
TearDown to abort any live Transaction* txn (or call tm.abort(txn) if not
nullptr), close/remove the backing file "./test_data/rollback_stress.heap" (and
any other test_data artifacts), and release/destroy bpm and disk_manager
resources; change the test to TEST_F(TransactionCoverageTests, DeepRollback) and
remove the inline static_cast<void>(std::remove(...)) cleanup line so the
TearDown handles it. Ensure the fixture exposes the symbols used in the test
(tm, txn, table, disk_manager, bpm) so the existing test body can be reused
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/transaction_coverage_tests.cpp`:
- Around line 78-135: Replace the standalone TEST( TransactionCoverageTests,
DeepRollback ) with a test fixture so cleanup always runs: create a class
TransactionCoverageTests : public ::testing::Test and implement SetUp to
initialize Catalog::create(), StorageManager disk_manager, BufferPoolManager
bpm, LockManager lm, TransactionManager tm, and create the HeapTable used by the
test; implement TearDown to abort any live Transaction* txn (or call
tm.abort(txn) if not nullptr), close/remove the backing file
"./test_data/rollback_stress.heap" (and any other test_data artifacts), and
release/destroy bpm and disk_manager resources; change the test to
TEST_F(TransactionCoverageTests, DeepRollback) and remove the inline
static_cast<void>(std::remove(...)) cleanup line so the TearDown handles it.
Ensure the fixture exposes the symbols used in the test (tm, txn, table,
disk_manager, bpm) so the existing test body can be reused unchanged.

In `@tests/utils_coverage_tests.cpp`:
- Around line 86-96: The test relies on reaching an unreachable service on
localhost (RpcClient constructor, connect/is_connected/call/send_only) which can
be flaky; replace the hardcoded localhost+port assumption by using a
non-routable TEST-NET address (e.g., 192.0.2.1) or, preferably, refactor to a
deterministic stub/mock of RpcClient and call the same methods (connect,
is_connected, call, send_only) to simulate failure; also add a short comment
explaining the chosen approach and why it avoids CI network variability so the
intent is clear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cee9e2c4-18cb-4b3f-a5ee-d83382d0efe3

📥 Commits

Reviewing files that changed from the base of the PR and between 6c8ea98 and c1b467c.

📒 Files selected for processing (4)
  • CMakeLists.txt
  • tests/catalog_coverage_tests.cpp
  • tests/transaction_coverage_tests.cpp
  • tests/utils_coverage_tests.cpp

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
tests/utils_coverage_tests.cpp (1)

42-42: Consider using EXPECT_EQ for std::string comparison.

While EXPECT_STREQ with .c_str() works, EXPECT_EQ is more idiomatic for std::string comparisons in gtest and provides clearer error messages.

📝 Suggested refactor
-    EXPECT_STREQ(v_null.to_string().c_str(), "NULL");
+    EXPECT_EQ(v_null.to_string(), "NULL");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/utils_coverage_tests.cpp` at line 42, The test currently uses
EXPECT_STREQ(v_null.to_string().c_str(), "NULL"); — replace this with the
idiomatic gtest string comparison using EXPECT_EQ by calling
EXPECT_EQ(v_null.to_string(), "NULL") (i.e., reference the v_null.to_string()
method and remove the .c_str() + EXPECT_STREQ usage so failures show clearer
std::string diffs).
tests/transaction_coverage_tests.cpp (2)

37-38: Isolate the on-disk state per test instance.

Hard-coding ./test_data and deleting only one heap file makes this fixture sensitive to stale artifacts. A per-test temp directory with recursive cleanup will keep reruns and parallel execution from bleeding state into DeepRollback.

Also applies to: 68-68

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/transaction_coverage_tests.cpp` around lines 37 - 38, The test fixture
currently hardcodes "./test_data" when creating StorageManager (disk_manager)
and only deletes a single heap file, which allows stale on-disk state to leak
between tests; change the test setup to create a unique per-test temporary
directory (e.g., using std::filesystem::temp_directory_path() combined with a
unique suffix like pid/timestamp/UUID) and construct StorageManager with that
path (where disk_manager is created), and in the teardown remove the entire
directory recursively (not just one heap file) to ensure full cleanup; apply the
same change for the other occurrence around line 68 so all tests (including
DeepRollback) use isolated temp dirs and recursive cleanup.

95-123: Use explicit synchronization instead of sleep/poll/yield.

Line 110 only proves that no reader incremented within 200 ms; it does not guarantee all readers were actually blocked on the writer. Replacing the timing windows and spin loop with a condition variable/latch will make this test deterministic and much less CPU-hungry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/transaction_coverage_tests.cpp` around lines 95 - 123, The test uses
sleeps and busy-wait (std::this_thread::sleep_for / yield) to assert readers are
blocked, which is flaky and CPU-heavy; replace that with explicit
synchronization: have the reader thread lambda signal a condition_variable or
count down a std::latch (or increment an atomic and notify on a
condition_variable) after successfully acquiring the shared lock, and have the
main thread wait on that condition_variable or latch to assert no readers have
progressed while the writer holds the lock and to wait deterministically for all
readers after writer_txn is unlocked; update uses of shared_granted, stop,
readers, lm.acquire_shared, lm.unlock and EXPECT_EQ to coordinate via the chosen
synchronization primitive instead of sleep/poll/yield so the test becomes
deterministic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/transaction_coverage_tests.cpp`:
- Around line 133-168: The test currently creates all rows inside the same
transaction that is later aborted, so restore semantics for UPDATE.old_rid and
DELETE undo aren’t exercised; modify the test to seed one or more committed rows
first (use a separate transaction that calls table->insert and commits via the
transaction manager before starting the txn used for modifications), then in the
transaction under test perform table->remove, table->insert and
txn->add_undo_log (including UndoLog::Type::UPDATE with the old_rid and
UndoLog::Type::DELETE), call tm->abort(txn), and finally assert via
table->tuple_count() and table->scan() that the original committed rows are
visible again; alternatively split into two tests where one seeds committed rows
and the other aborts modifications to them.

---

Nitpick comments:
In `@tests/transaction_coverage_tests.cpp`:
- Around line 37-38: The test fixture currently hardcodes "./test_data" when
creating StorageManager (disk_manager) and only deletes a single heap file,
which allows stale on-disk state to leak between tests; change the test setup to
create a unique per-test temporary directory (e.g., using
std::filesystem::temp_directory_path() combined with a unique suffix like
pid/timestamp/UUID) and construct StorageManager with that path (where
disk_manager is created), and in the teardown remove the entire directory
recursively (not just one heap file) to ensure full cleanup; apply the same
change for the other occurrence around line 68 so all tests (including
DeepRollback) use isolated temp dirs and recursive cleanup.
- Around line 95-123: The test uses sleeps and busy-wait
(std::this_thread::sleep_for / yield) to assert readers are blocked, which is
flaky and CPU-heavy; replace that with explicit synchronization: have the reader
thread lambda signal a condition_variable or count down a std::latch (or
increment an atomic and notify on a condition_variable) after successfully
acquiring the shared lock, and have the main thread wait on that
condition_variable or latch to assert no readers have progressed while the
writer holds the lock and to wait deterministically for all readers after
writer_txn is unlocked; update uses of shared_granted, stop, readers,
lm.acquire_shared, lm.unlock and EXPECT_EQ to coordinate via the chosen
synchronization primitive instead of sleep/poll/yield so the test becomes
deterministic.

In `@tests/utils_coverage_tests.cpp`:
- Line 42: The test currently uses EXPECT_STREQ(v_null.to_string().c_str(),
"NULL"); — replace this with the idiomatic gtest string comparison using
EXPECT_EQ by calling EXPECT_EQ(v_null.to_string(), "NULL") (i.e., reference the
v_null.to_string() method and remove the .c_str() + EXPECT_STREQ usage so
failures show clearer std::string diffs).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6acaf55a-a4f7-4249-bed3-5781540a4f6a

📥 Commits

Reviewing files that changed from the base of the PR and between c1b467c and 065a609.

📒 Files selected for processing (2)
  • tests/transaction_coverage_tests.cpp
  • tests/utils_coverage_tests.cpp

Comment on lines +133 to +168
// 1. Insert some data
auto rid1 = table->insert(
executor::Tuple({common::Value::make_int64(1), common::Value::make_text("A")}),
txn->get_id());
txn->add_undo_log(UndoLog::Type::INSERT, "rollback_stress", rid1);

auto rid2 = table->insert(
executor::Tuple({common::Value::make_int64(2), common::Value::make_text("B")}),
txn->get_id());
txn->add_undo_log(UndoLog::Type::INSERT, "rollback_stress", rid2);

// 2. Update data
table->remove(rid1, txn->get_id()); // Mark old version deleted
auto rid1_new = table->insert(
executor::Tuple({common::Value::make_int64(1), common::Value::make_text("A_NEW")}),
txn->get_id());
txn->add_undo_log(UndoLog::Type::UPDATE, "rollback_stress", rid1_new, rid1);

// 3. Delete data
table->remove(rid2, txn->get_id());
txn->add_undo_log(UndoLog::Type::DELETE, "rollback_stress", rid2);

EXPECT_EQ(table->tuple_count(), 1U); // rid1_new is active, rid1 and rid2 are logically deleted

// 4. Abort
tm->abort(txn);
txn = nullptr; // Marked as aborted and handled by TearDown if still set

// 5. Verify restoration
EXPECT_EQ(table->tuple_count(),
0U); // Inserted rows should be physically removed or logically invisible

// The table should be empty because we aborted the inserts
auto iter = table->scan();
executor::Tuple t;
EXPECT_FALSE(iter.next(t));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Seed committed rows if you want to verify restore semantics.

Because every tuple in this scenario is created by the transaction that later aborts, the final tuple_count() == 0 assertion only proves “abort returns the table to empty.” It does not make the restoration half of DELETE undo or the old_rid half of UPDATE undo observable. Seed committed rows first, or split this into a second test, then abort a transaction that updates/deletes them and assert the original rows are visible again.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/transaction_coverage_tests.cpp` around lines 133 - 168, The test
currently creates all rows inside the same transaction that is later aborted, so
restore semantics for UPDATE.old_rid and DELETE undo aren’t exercised; modify
the test to seed one or more committed rows first (use a separate transaction
that calls table->insert and commits via the transaction manager before starting
the txn used for modifications), then in the transaction under test perform
table->remove, table->insert and txn->add_undo_log (including
UndoLog::Type::UPDATE with the old_rid and UndoLog::Type::DELETE), call
tm->abort(txn), and finally assert via table->tuple_count() and table->scan()
that the original committed rows are visible again; alternatively split into two
tests where one seeds committed rows and the other aborts modifications to them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant