Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Add LCOV_EXCL_LINE markers to LOG() statements for coverage analysis
- Added LCOV_EXCL_LINE markers to 284 LOG() diagnostic statements in C++ code
- Excludes debug/diagnostic logging from code coverage metrics
- Affected files: connection.cpp, connection_pool.cpp, ddbc_bindings.cpp
- Includes automation script (add_lcov_exclusions.py) for future maintenance
- Expected coverage improvement: ~3-5% (excluding non-functional diagnostic lines)
  • Loading branch information
gargsaumya committed May 5, 2026
commit 066619391943d2c3e1e3b0bc96a83552f23b1e26
66 changes: 66 additions & 0 deletions add_lcov_exclusions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""
Script to add LCOV_EXCL_LINE markers to LOG() statements in C++ code.
This excludes diagnostic logging from code coverage analysis.
"""

import re
from pathlib import Path

def process_file(filepath):
"""Add LCOV_EXCL_LINE to LOG() statements in a C++ file."""
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()

lines = content.split('\n')
modified = False
new_lines = []

for line in lines:
# Check if line contains LOG( and doesn't already have LCOV_EXCL
if 'LOG(' in line and 'LCOV_EXCL' not in line:
# Skip comment lines that mention LOG() but aren't actual LOG calls
if line.strip().startswith('//'):
new_lines.append(line)
continue

# Add marker at end of line (before newline)
# Handle cases where line already has trailing comment
stripped = line.rstrip()
if stripped.endswith(';'):
new_line = stripped + ' // LCOV_EXCL_LINE'
else:
new_line = stripped + ' // LCOV_EXCL_LINE'

new_lines.append(new_line)
modified = True
print(f" Modified: {filepath.name}:{len(new_lines)} - {stripped[:60]}...")
else:
new_lines.append(line)

if modified:
with open(filepath, 'w', encoding='utf-8') as f:
f.write('\n'.join(new_lines))
return True
return False

def main():
"""Process all C++ files in mssql_python/pybind directory."""
base_path = Path(__file__).parent / 'mssql_python' / 'pybind'

# Find all .cpp and .h files
cpp_files = list(base_path.rglob('*.cpp')) + list(base_path.rglob('*.h'))

print(f"Found {len(cpp_files)} C++ files to process")
print("=" * 70)

modified_count = 0
for filepath in sorted(cpp_files):
if process_file(filepath):
modified_count += 1

print("=" * 70)
print(f"\nCompleted: {modified_count} files modified")

if __name__ == '__main__':
main()
76 changes: 38 additions & 38 deletions mssql_python/pybind/connection/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@

static SqlHandlePtr getEnvHandle() {
static SqlHandlePtr envHandle = []() -> SqlHandlePtr {
LOG("Allocating ODBC environment handle");
LOG("Allocating ODBC environment handle"); // LCOV_EXCL_LINE
if (!SQLAllocHandle_ptr) {
LOG("Function pointers not initialized, loading driver");
LOG("Function pointers not initialized, loading driver"); // LCOV_EXCL_LINE
DriverLoader::getInstance().loadDriver();
}
SQLHANDLE env = nullptr;
Expand Down Expand Up @@ -58,30 +58,30 @@ Connection::~Connection() {
void Connection::allocateDbcHandle() {
auto _envHandle = getEnvHandle();
SQLHANDLE dbc = nullptr;
LOG("Allocating SQL Connection Handle");
LOG("Allocating SQL Connection Handle"); // LCOV_EXCL_LINE
SQLRETURN ret = SQLAllocHandle_ptr(SQL_HANDLE_DBC, _envHandle->get(), &dbc);
checkError(ret);
_dbcHandle = std::make_shared<SqlHandle>(static_cast<SQLSMALLINT>(SQL_HANDLE_DBC), dbc);
}

void Connection::connect(const py::dict& attrs_before) {
LOG("Connecting to database");
LOG("Connecting to database"); // LCOV_EXCL_LINE
// Apply access token before connect
if (!attrs_before.is_none() && py::len(attrs_before) > 0) {
LOG("Apply attributes before connect");
LOG("Apply attributes before connect"); // LCOV_EXCL_LINE
applyAttrsBefore(attrs_before);
if (_autocommit) {
setAutocommit(_autocommit);
}
}
SQLWCHAR* connStrPtr;
#if defined(__APPLE__) || defined(__linux__) // macOS/Linux handling
LOG("Creating connection string buffer for macOS/Linux");
LOG("Creating connection string buffer for macOS/Linux"); // LCOV_EXCL_LINE
std::vector<SQLWCHAR> connStrBuffer = WStringToSQLWCHAR(_connStr);
// Ensure the buffer is null-terminated
LOG("Connection string buffer size=%zu", connStrBuffer.size());
LOG("Connection string buffer size=%zu", connStrBuffer.size()); // LCOV_EXCL_LINE
connStrPtr = connStrBuffer.data();
LOG("Connection string buffer created");
LOG("Connection string buffer created"); // LCOV_EXCL_LINE
#else
connStrPtr = const_cast<SQLWCHAR*>(_connStr.c_str());
#endif
Expand All @@ -101,7 +101,7 @@ void Connection::connect(const py::dict& attrs_before) {

void Connection::disconnect() {
if (_dbcHandle) {
LOG("Disconnecting from database");
LOG("Disconnecting from database"); // LCOV_EXCL_LINE

// Check if we hold the GIL so we can conditionally release it.
// The GIL is held when called from pybind11-bound methods but may NOT
Expand All @@ -125,11 +125,11 @@ void Connection::disconnect() {
[](const std::weak_ptr<SqlHandle>& wp) { return wp.expired(); }),
_childStatementHandles.end());

LOG("Compacted child handles: %zu -> %zu (removed %zu expired)",
LOG("Compacted child handles: %zu -> %zu (removed %zu expired)", // LCOV_EXCL_LINE
originalSize, _childStatementHandles.size(),
originalSize - _childStatementHandles.size());

LOG("Marking %zu child statement handles as implicitly freed",
LOG("Marking %zu child statement handles as implicitly freed", // LCOV_EXCL_LINE
_childStatementHandles.size());
for (auto& weakHandle : _childStatementHandles) {
if (auto handle = weakHandle.lock()) {
Expand Down Expand Up @@ -171,7 +171,7 @@ void Connection::disconnect() {
// triggers SQLFreeHandle via destructor, if last owner
_dbcHandle.reset();
} else {
LOG("No connection handle to disconnect");
LOG("No connection handle to disconnect"); // LCOV_EXCL_LINE
}
}

Expand All @@ -190,7 +190,7 @@ void Connection::commit() {
ThrowStdException("Connection handle not allocated");
}
updateLastUsed();
LOG("Committing transaction");
LOG("Committing transaction"); // LCOV_EXCL_LINE
SQLRETURN ret;
{
// Release the GIL during the blocking SQLEndTran network round-trip.
Expand All @@ -205,7 +205,7 @@ void Connection::rollback() {
ThrowStdException("Connection handle not allocated");
}
updateLastUsed();
LOG("Rolling back transaction");
LOG("Rolling back transaction"); // LCOV_EXCL_LINE
SQLRETURN ret;
{
// Release the GIL during the blocking SQLEndTran network round-trip.
Expand All @@ -220,15 +220,15 @@ void Connection::setAutocommit(bool enable) {
ThrowStdException("Connection handle not allocated");
}
SQLINTEGER value = enable ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF;
LOG("Setting autocommit=%d", enable);
LOG("Setting autocommit=%d", enable); // LCOV_EXCL_LINE
SQLRETURN ret =
SQLSetConnectAttr_ptr(_dbcHandle->get(), SQL_ATTR_AUTOCOMMIT,
reinterpret_cast<SQLPOINTER>(static_cast<SQLULEN>(value)), 0);
checkError(ret);
if (value == SQL_AUTOCOMMIT_ON) {
LOG("Autocommit enabled");
LOG("Autocommit enabled"); // LCOV_EXCL_LINE
} else {
LOG("Autocommit disabled");
LOG("Autocommit disabled"); // LCOV_EXCL_LINE
}
_autocommit = enable;
}
Expand All @@ -237,7 +237,7 @@ bool Connection::getAutocommit() const {
if (!_dbcHandle) {
ThrowStdException("Connection handle not allocated");
}
LOG("Getting autocommit attribute");
LOG("Getting autocommit attribute"); // LCOV_EXCL_LINE
SQLINTEGER value;
SQLINTEGER string_length;
SQLRETURN ret = SQLGetConnectAttr_ptr(_dbcHandle->get(), SQL_ATTR_AUTOCOMMIT, &value,
Expand All @@ -251,7 +251,7 @@ SqlHandlePtr Connection::allocStatementHandle() {
ThrowStdException("Connection handle not allocated");
}
updateLastUsed();
LOG("Allocating statement handle");
LOG("Allocating statement handle"); // LCOV_EXCL_LINE
SQLHANDLE stmt = nullptr;
SQLRETURN ret = SQLAllocHandle_ptr(SQL_HANDLE_STMT, _dbcHandle->get(), &stmt);
checkError(ret);
Expand All @@ -278,7 +278,7 @@ SqlHandlePtr Connection::allocStatementHandle() {
[](const std::weak_ptr<SqlHandle>& wp) { return wp.expired(); }),
_childStatementHandles.end());
_allocationsSinceCompaction = 0;
LOG("Periodic compaction: %zu -> %zu handles (removed %zu expired)",
LOG("Periodic compaction: %zu -> %zu handles (removed %zu expired)", // LCOV_EXCL_LINE
originalSize, _childStatementHandles.size(),
originalSize - _childStatementHandles.size());
}
Expand All @@ -288,7 +288,7 @@ SqlHandlePtr Connection::allocStatementHandle() {
}

SQLRETURN Connection::setAttribute(SQLINTEGER attribute, py::object value) {
LOG("Setting SQL attribute=%d", attribute);
LOG("Setting SQL attribute=%d", attribute); // LCOV_EXCL_LINE
// SQLPOINTER ptr = nullptr;
// SQLINTEGER length = 0;

Expand All @@ -301,9 +301,9 @@ SQLRETURN Connection::setAttribute(SQLINTEGER attribute, py::object value) {
reinterpret_cast<SQLPOINTER>(static_cast<SQLULEN>(longValue)), SQL_IS_INTEGER);

if (!SQL_SUCCEEDED(ret)) {
LOG("Failed to set integer attribute=%d, ret=%d", attribute, ret);
LOG("Failed to set integer attribute=%d, ret=%d", attribute, ret); // LCOV_EXCL_LINE
} else {
LOG("Set integer attribute=%d successfully", attribute);
LOG("Set integer attribute=%d successfully", attribute); // LCOV_EXCL_LINE
}
return ret;
} else if (py::isinstance<py::str>(value)) {
Expand All @@ -313,7 +313,7 @@ SQLRETURN Connection::setAttribute(SQLINTEGER attribute, py::object value) {
// Convert to wide string
std::wstring wstr = Utf8ToWString(utf8_str);
if (wstr.empty() && !utf8_str.empty()) {
LOG("Failed to convert string value to wide string for "
LOG("Failed to convert string value to wide string for " // LCOV_EXCL_LINE
"attribute=%d",
attribute);
return SQL_ERROR;
Expand All @@ -328,7 +328,7 @@ SQLRETURN Connection::setAttribute(SQLINTEGER attribute, py::object value) {
// For macOS/Linux, convert wstring to SQLWCHAR buffer
std::vector<SQLWCHAR> sqlwcharBuffer = WStringToSQLWCHAR(this->wstrStringBuffer);
if (sqlwcharBuffer.empty() && !this->wstrStringBuffer.empty()) {
LOG("Failed to convert wide string to SQLWCHAR buffer for "
LOG("Failed to convert wide string to SQLWCHAR buffer for " // LCOV_EXCL_LINE
"attribute=%d",
attribute);
return SQL_ERROR;
Expand All @@ -344,13 +344,13 @@ SQLRETURN Connection::setAttribute(SQLINTEGER attribute, py::object value) {

SQLRETURN ret = SQLSetConnectAttr_ptr(_dbcHandle->get(), attribute, ptr, length);
if (!SQL_SUCCEEDED(ret)) {
LOG("Failed to set string attribute=%d, ret=%d", attribute, ret);
LOG("Failed to set string attribute=%d, ret=%d", attribute, ret); // LCOV_EXCL_LINE
} else {
LOG("Set string attribute=%d successfully", attribute);
LOG("Set string attribute=%d successfully", attribute); // LCOV_EXCL_LINE
}
return ret;
} catch (const std::exception& e) {
LOG("Exception during string attribute=%d setting: %s", attribute, e.what());
LOG("Exception during string attribute=%d setting: %s", attribute, e.what()); // LCOV_EXCL_LINE
return SQL_ERROR;
}
} else if (py::isinstance<py::bytes>(value) || py::isinstance<py::bytearray>(value)) {
Expand All @@ -363,17 +363,17 @@ SQLRETURN Connection::setAttribute(SQLINTEGER attribute, py::object value) {

SQLRETURN ret = SQLSetConnectAttr_ptr(_dbcHandle->get(), attribute, ptr, length);
if (!SQL_SUCCEEDED(ret)) {
LOG("Failed to set binary attribute=%d, ret=%d", attribute, ret);
LOG("Failed to set binary attribute=%d, ret=%d", attribute, ret); // LCOV_EXCL_LINE
} else {
LOG("Set binary attribute=%d successfully (length=%d)", attribute, length);
LOG("Set binary attribute=%d successfully (length=%d)", attribute, length); // LCOV_EXCL_LINE
}
return ret;
} catch (const std::exception& e) {
LOG("Exception during binary attribute=%d setting: %s", attribute, e.what());
LOG("Exception during binary attribute=%d setting: %s", attribute, e.what()); // LCOV_EXCL_LINE
return SQL_ERROR;
}
} else {
LOG("Unsupported attribute value type for attribute=%d", attribute);
LOG("Unsupported attribute value type for attribute=%d", attribute); // LCOV_EXCL_LINE
return SQL_ERROR;
}
}
Expand Down Expand Up @@ -411,22 +411,22 @@ bool Connection::reset() {
if (!_dbcHandle) {
ThrowStdException("Connection handle not allocated");
}
LOG("Resetting connection via SQL_ATTR_RESET_CONNECTION");
LOG("Resetting connection via SQL_ATTR_RESET_CONNECTION"); // LCOV_EXCL_LINE
SQLRETURN ret = SQLSetConnectAttr_ptr(_dbcHandle->get(), SQL_ATTR_RESET_CONNECTION,
(SQLPOINTER)SQL_RESET_CONNECTION_YES, SQL_IS_INTEGER);
if (!SQL_SUCCEEDED(ret)) {
LOG("Failed to reset connection (ret=%d). Marking as dead.", ret);
LOG("Failed to reset connection (ret=%d). Marking as dead.", ret); // LCOV_EXCL_LINE
return false;
}

// SQL_ATTR_RESET_CONNECTION does NOT reset the transaction isolation level.
// Explicitly reset it to the default (SQL_TXN_READ_COMMITTED) to prevent
// isolation level settings from leaking between pooled connection usages.
LOG("Resetting transaction isolation level to READ COMMITTED");
LOG("Resetting transaction isolation level to READ COMMITTED"); // LCOV_EXCL_LINE
ret = SQLSetConnectAttr_ptr(_dbcHandle->get(), SQL_ATTR_TXN_ISOLATION,
(SQLPOINTER)SQL_TXN_READ_COMMITTED, SQL_IS_INTEGER);
if (!SQL_SUCCEEDED(ret)) {
LOG("Failed to reset transaction isolation level (ret=%d). Marking as dead.", ret);
LOG("Failed to reset transaction isolation level (ret=%d). Marking as dead.", ret); // LCOV_EXCL_LINE
return false;
}

Expand Down Expand Up @@ -591,13 +591,13 @@ void ConnectionHandle::setAttr(int attribute, py::object value) {
errorMsg += ": " + ddbcErrorStr;
}

LOG("Connection setAttribute failed: %s", errorMsg.c_str());
LOG("Connection setAttribute failed: %s", errorMsg.c_str()); // LCOV_EXCL_LINE
ThrowStdException(errorMsg);
} catch (...) {
// Fallback to generic error if detailed error retrieval fails
std::string errorMsg =
"Failed to set connection attribute " + std::to_string(attribute);
LOG("Connection setAttribute failed: %s", errorMsg.c_str());
LOG("Connection setAttribute failed: %s", errorMsg.c_str()); // LCOV_EXCL_LINE
ThrowStdException(errorMsg);
}
}
Expand Down
8 changes: 4 additions & 4 deletions mssql_python/pybind/connection/connection_pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ std::shared_ptr<Connection> ConnectionPool::acquire(const std::wstring& connStr,
try {
conn->disconnect();
} catch (const std::exception& ex) {
LOG("Disconnect bad/expired connections failed: %s", ex.what());
LOG("Disconnect bad/expired connections failed: %s", ex.what()); // LCOV_EXCL_LINE
}
}
return valid_conn;
Expand All @@ -113,7 +113,7 @@ void ConnectionPool::release(std::shared_ptr<Connection> conn) {
try {
conn->disconnect();
} catch (const std::exception& ex) {
LOG("ConnectionPool::release: disconnect failed: %s", ex.what());
LOG("ConnectionPool::release: disconnect failed: %s", ex.what()); // LCOV_EXCL_LINE
}
std::lock_guard<std::mutex> lock(_mutex);
if (_current_size > 0)
Expand All @@ -135,7 +135,7 @@ void ConnectionPool::close() {
try {
conn->disconnect();
} catch (const std::exception& ex) {
LOG("ConnectionPool::close: disconnect failed: %s", ex.what());
LOG("ConnectionPool::close: disconnect failed: %s", ex.what()); // LCOV_EXCL_LINE
}
}
}
Expand All @@ -152,7 +152,7 @@ std::shared_ptr<Connection> ConnectionPoolManager::acquireConnection(const std::
std::lock_guard<std::mutex> lock(_manager_mutex);
auto& pool_ref = _pools[connStr];
if (!pool_ref) {
LOG("Creating new connection pool");
LOG("Creating new connection pool"); // LCOV_EXCL_LINE
pool_ref = std::make_shared<ConnectionPool>(_default_max_size, _default_idle_secs);
}
pool = pool_ref;
Expand Down
Loading
Loading