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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ include(CMakeDependentOption)
# provides a method to download dependencies:
include(FetchContent)
include(CMakeHelpers/MdioHelpers)
# optional code coverage support:
include(CodeCoverage)

list(APPEND mdio_DEFAULT_COPTS
"-Wno-deprecated-declarations"
Expand Down
6 changes: 6 additions & 0 deletions cmake/CMakeHelpers/MdioHelpers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,12 @@ function(mdio_cc_test)
PRIVATE ${mdio_CC_TEST_COPTS}
)

# Add coverage flags only to mdio test targets (not dependencies)
if(MDIO_ENABLE_COVERAGE)
target_compile_options(${_NAME} PRIVATE ${MDIO_COVERAGE_COMPILE_FLAGS})
target_link_options(${_NAME} PRIVATE ${MDIO_COVERAGE_LINK_FLAGS})
endif()

target_link_libraries(${_NAME}
PUBLIC ${mdio_CC_TEST_DEPS}
PRIVATE ${mdio_CC_TEST_LINKOPTS}
Expand Down
166 changes: 166 additions & 0 deletions cmake/CodeCoverage.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Copyright 2024 TGS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# ==============================================================================
# Code Coverage Support (gcov + lcov)
# ==============================================================================
#
# Coverage instrumentation is applied ONLY to mdio test targets, not to
# dependencies like Tensorstore. This keeps build times and test execution fast.
#
# REQUIRED CMAKE FLAGS:
# -DMDIO_ENABLE_COVERAGE=ON Enable coverage instrumentation
# -DCMAKE_BUILD_TYPE=Debug Recommended for meaningful line coverage
#
# REQUIRED SYSTEM TOOLS:
# - gcov (usually bundled with GCC)
# - lcov (install via: apt install lcov / brew install lcov)
# - genhtml (included with lcov)
#
# USAGE:
# 1. Configure and build with coverage enabled:
# cd build
# cmake .. -DMDIO_ENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug
# make
#
# 2. Run tests to generate coverage data (coverage accumulates across runs):
# ./mdio/mdio_variable_test # single test
# ./mdio/mdio_variable_test && ./mdio/mdio_dataset_test # multiple tests
# ctest # all registered tests
#
# 3. Generate HTML coverage report:
# make coverage-capture
#
# 4. View the report:
# Open build/coverage_report/index.html in a browser
#
# AVAILABLE TARGETS:
# make coverage - Reset counters, capture data, generate report
# make coverage-capture - Capture current data and generate report (no reset)
# make coverage-reset - Zero out all coverage counters
#
# ==============================================================================

option(MDIO_ENABLE_COVERAGE "Enable code coverage instrumentation (requires GCC or Clang)" OFF)

if(MDIO_ENABLE_COVERAGE)
message(STATUS "Code coverage enabled")

# Check for supported compiler
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
message(FATAL_ERROR "Code coverage requires GCC or Clang compiler")
endif()

# Coverage compiler/linker flags - exported for use in MdioHelpers.cmake
# These are applied only to mdio targets, not to dependencies like Tensorstore
set(MDIO_COVERAGE_COMPILE_FLAGS "-fprofile-arcs;-ftest-coverage" CACHE INTERNAL "Coverage compile flags")
set(MDIO_COVERAGE_LINK_FLAGS "--coverage" CACHE INTERNAL "Coverage link flags")

# Find required tools
find_program(LCOV_PATH lcov)
find_program(GENHTML_PATH genhtml)

if(NOT LCOV_PATH)
message(WARNING "lcov not found - coverage report generation will not be available")
endif()

if(NOT GENHTML_PATH)
message(WARNING "genhtml not found - coverage report generation will not be available")
endif()

# Create coverage report target if tools are available
if(LCOV_PATH AND GENHTML_PATH)
# Custom target to generate coverage report
add_custom_target(coverage
COMMENT "Generating code coverage report..."

# Clear previous coverage data
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters

# Run all tests (ctest must be run separately or you can uncomment below)
# COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure

# Capture coverage data
COMMAND ${LCOV_PATH}
--directory ${CMAKE_BINARY_DIR}
--capture
--output-file ${CMAKE_BINARY_DIR}/coverage.info
--ignore-errors mismatch,negative

# Remove coverage data for external dependencies
COMMAND ${LCOV_PATH}
--remove ${CMAKE_BINARY_DIR}/coverage.info
'/usr/*'
'${CMAKE_BINARY_DIR}/_deps/*'
'*/googletest/*'
'*/gtest/*'
'*/gmock/*'
'*_test.cc'
--output-file ${CMAKE_BINARY_DIR}/coverage.info
--ignore-errors unused,negative

# Generate HTML report
COMMAND ${GENHTML_PATH}
${CMAKE_BINARY_DIR}/coverage.info
--output-directory ${CMAKE_BINARY_DIR}/coverage_report
--title "MDIO Code Coverage"
--legend
--show-details

WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

# Target to just capture coverage (without zeroing first)
add_custom_target(coverage-capture
COMMENT "Capturing coverage data..."

COMMAND ${LCOV_PATH}
--directory ${CMAKE_BINARY_DIR}
--capture
--output-file ${CMAKE_BINARY_DIR}/coverage.info
--ignore-errors mismatch,negative

COMMAND ${LCOV_PATH}
--remove ${CMAKE_BINARY_DIR}/coverage.info
'/usr/*'
'${CMAKE_BINARY_DIR}/_deps/*'
'*/googletest/*'
'*/gtest/*'
'*/gmock/*'
'*_test.cc'
--output-file ${CMAKE_BINARY_DIR}/coverage.info
--ignore-errors unused,negative

COMMAND ${GENHTML_PATH}
${CMAKE_BINARY_DIR}/coverage.info
--output-directory ${CMAKE_BINARY_DIR}/coverage_report
--title "MDIO Code Coverage"
--legend
--show-details

WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

# Target to reset coverage counters
add_custom_target(coverage-reset
COMMENT "Resetting coverage counters..."
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

message(STATUS "Coverage targets available: 'coverage', 'coverage-capture', 'coverage-reset'")
message(STATUS "Coverage report will be generated at: ${CMAKE_BINARY_DIR}/coverage_report/index.html")
endif()
endif()

56 changes: 56 additions & 0 deletions mdio/acceptance_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,42 @@ TEST_P(VariableTest, SETUP) {
auto f8 =
mdio::internal::CreateVariable(f8Store, f8Metadata, std::move(options));
ASSERT_TRUE(f8.status().ok()) << f8.status();

// Create u1 (uint8)
auto u1Spec = CreateTypedVariableSpec(version_, "u1", base_path_, "<u1",
"uint8", "1-byte unsigned integer test");
auto u1Schema = mdio::internal::ValidateAndProcessJson(u1Spec).value();
auto [u1Store, u1Metadata] = u1Schema;
auto u1 =
mdio::internal::CreateVariable(u1Store, u1Metadata, std::move(options));
ASSERT_TRUE(u1.status().ok()) << u1.status();

// Create u2 (uint16)
auto u2Spec = CreateTypedVariableSpec(version_, "u2", base_path_, "<u2",
"uint16", "2-byte unsigned integer test");
auto u2Schema = mdio::internal::ValidateAndProcessJson(u2Spec).value();
auto [u2Store, u2Metadata] = u2Schema;
auto u2 =
mdio::internal::CreateVariable(u2Store, u2Metadata, std::move(options));
ASSERT_TRUE(u2.status().ok()) << u2.status();

// Create u8 (uint64)
auto u8Spec = CreateTypedVariableSpec(version_, "u8", base_path_, "<u8",
"uint64", "8-byte unsigned integer test");
auto u8Schema = mdio::internal::ValidateAndProcessJson(u8Spec).value();
auto [u8Store, u8Metadata] = u8Schema;
auto u8 =
mdio::internal::CreateVariable(u8Store, u8Metadata, std::move(options));
ASSERT_TRUE(u8.status().ok()) << u8.status();

// Create b1 (bool)
auto b1Spec = CreateTypedVariableSpec(version_, "b1", base_path_, "|b1",
"bool", "boolean test");
auto b1Schema = mdio::internal::ValidateAndProcessJson(b1Spec).value();
auto [b1Store, b1Metadata] = b1Schema;
auto b1 =
mdio::internal::CreateVariable(b1Store, b1Metadata, std::move(options));
ASSERT_TRUE(b1.status().ok()) << b1.status();
}

TEST_P(VariableTest, open) {
Expand Down Expand Up @@ -708,20 +744,36 @@ TEST_P(VariableTest, dtype) {
mdio::constants::kOpen);
auto f8 = mdio::Variable<>::Open(CreateBaseSpec(version_, "f8", base_path_),
mdio::constants::kOpen);
auto u1 = mdio::Variable<>::Open(CreateBaseSpec(version_, "u1", base_path_),
mdio::constants::kOpen);
auto u2 = mdio::Variable<>::Open(CreateBaseSpec(version_, "u2", base_path_),
mdio::constants::kOpen);
auto u8 = mdio::Variable<>::Open(CreateBaseSpec(version_, "u8", base_path_),
mdio::constants::kOpen);
auto b1 = mdio::Variable<>::Open(CreateBaseSpec(version_, "b1", base_path_),
mdio::constants::kOpen);

ASSERT_TRUE(i2.status().ok()) << i2.status();
ASSERT_TRUE(i4.status().ok()) << i4.status();
ASSERT_TRUE(i8.status().ok()) << i8.status();
ASSERT_TRUE(f2.status().ok()) << f2.status();
ASSERT_TRUE(f4.status().ok()) << f4.status();
ASSERT_TRUE(f8.status().ok()) << f8.status();
ASSERT_TRUE(u1.status().ok()) << u1.status();
ASSERT_TRUE(u2.status().ok()) << u2.status();
ASSERT_TRUE(u8.status().ok()) << u8.status();
ASSERT_TRUE(b1.status().ok()) << b1.status();

EXPECT_EQ(i2.value().dtype(), mdio::constants::kInt16);
EXPECT_EQ(i4.value().dtype(), mdio::constants::kInt32);
EXPECT_EQ(i8.value().dtype(), mdio::constants::kInt64);
EXPECT_EQ(f2.value().dtype(), mdio::constants::kFloat16);
EXPECT_EQ(f4.value().dtype(), mdio::constants::kFloat32);
EXPECT_EQ(f8.value().dtype(), mdio::constants::kFloat64);
EXPECT_EQ(u1.value().dtype(), mdio::constants::kUint8);
EXPECT_EQ(u2.value().dtype(), mdio::constants::kUint16);
EXPECT_EQ(u8.value().dtype(), mdio::constants::kUint64);
EXPECT_EQ(b1.value().dtype(), mdio::constants::kBool);
}

TEST_P(VariableTest, domain) {
Expand All @@ -741,6 +793,10 @@ TEST_P(VariableTest, TEARDOWN) {
std::filesystem::remove_all(base_path_ + "/f2");
std::filesystem::remove_all(base_path_ + "/f4");
std::filesystem::remove_all(base_path_ + "/f8");
std::filesystem::remove_all(base_path_ + "/u1");
std::filesystem::remove_all(base_path_ + "/u2");
std::filesystem::remove_all(base_path_ + "/u8");
std::filesystem::remove_all(base_path_ + "/b1");
ASSERT_TRUE(true);
}

Expand Down
Loading