diff --git a/.gitignore b/.gitignore index d09646d..68a92a3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ build-* .cache webserv.log compile_commands.json -iwyu_results/ \ No newline at end of file +iwyu_results/ +build_coverage/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 33fa0c6..8b2c48b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,8 @@ include_directories( # Build type options +option(ENABLE_COVERAGE "Enable code coverage" OFF) + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() @@ -45,6 +47,58 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +# Coverage configuration +if(ENABLE_COVERAGE) + message(STATUS "Code coverage enabled") + + # Find required tools + find_program(GCOV_PATH gcov) + find_program(LCOV_PATH lcov) + find_program(GENHTML_PATH genhtml) + find_program(GCOVR_PATH gcovr) + + if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Please install gcov. On Ubuntu: sudo apt-get install gcc") + endif() + + # Check for coverage tools (prefer lcov, fallback to gcovr) + if(LCOV_PATH AND GENHTML_PATH) + message(STATUS "Found lcov and genhtml - using lcov for coverage reports") + set(COVERAGE_TOOL "lcov") + elseif(GCOVR_PATH) + message(STATUS "Found gcovr - using gcovr for coverage reports") + set(COVERAGE_TOOL "gcovr") + else() + message(STATUS "Coverage tools not found. Install with:") + message(STATUS " Ubuntu: sudo apt-get install lcov") + message(STATUS " Or: pip install gcovr") + message(STATUS "Coverage data will still be generated, but no HTML report") + set(COVERAGE_TOOL "none") + endif() + + # Set coverage flags based on compiler + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + message(STATUS "Using GCC coverage flags") + set(COVERAGE_FLAGS "--coverage -fprofile-arcs -ftest-coverage -fno-inline -fno-inline-small-functions -fno-default-inline") + set(COVERAGE_LINK_FLAGS "--coverage") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(STATUS "Using Clang coverage flags") + # Use --coverage for Clang to generate gcov-compatible data + set(COVERAGE_FLAGS "--coverage -fprofile-arcs -ftest-coverage") + set(COVERAGE_LINK_FLAGS "--coverage") + else() + message(FATAL_ERROR "Code coverage only supported with GCC or Clang") + endif() + + # Apply coverage flags + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${COVERAGE_LINK_FLAGS}") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${COVERAGE_LINK_FLAGS}") + + # Ensure debug information is available + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0") +endif() + # Set build flags for different build types if(CMAKE_BUILD_TYPE STREQUAL "Debug") message(STATUS "Debug build: adding debug flags") @@ -155,3 +209,103 @@ gtest_discover_tests(webserv_tests) endif() endif() +# Coverage targets +if(ENABLE_COVERAGE AND BUILD_TESTS) + # Coverage cleanup target + add_custom_target(coverage_clean + COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/coverage_report + COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_BINARY_DIR}/coverage.info + COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_BINARY_DIR}/coverage.xml + COMMENT "Cleaning coverage data" + ) + + if(COVERAGE_TOOL STREQUAL "lcov") + # Main coverage target using lcov + add_custom_target(coverage + DEPENDS webserv_tests + + # Cleanup previous coverage data + COMMAND ${LCOV_PATH} --directory . --zerocounters + + # Run tests + COMMAND ${CMAKE_CTEST_COMMAND} --verbose + + # Capture coverage data + COMMAND ${LCOV_PATH} --directory . --capture --output-file coverage.info + + # Remove system and test files from coverage + COMMAND ${LCOV_PATH} --remove coverage.info '/usr/*' '/opt/*' '*/tests/*' '*/test/*' '*/_deps/*' '*/build/*' --output-file coverage.info + + # Generate HTML report + COMMAND ${GENHTML_PATH} coverage.info --output-directory coverage_report --title "Webserv Coverage Report" --show-details --legend + + # Print summary + COMMAND ${LCOV_PATH} --summary coverage.info + + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Generating code coverage report with lcov" + ) + + # Coverage summary target + add_custom_target(coverage_summary + DEPENDS webserv_tests + COMMAND ${CMAKE_CTEST_COMMAND} --verbose + COMMAND ${LCOV_PATH} --directory . --capture --output-file coverage.info + COMMAND ${LCOV_PATH} --remove coverage.info '/usr/*' '/opt/*' '*/tests/*' '*/test/*' '*/_deps/*' '*/build/*' --output-file coverage.info + COMMAND ${LCOV_PATH} --summary coverage.info + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Generating coverage summary" + ) + + elseif(COVERAGE_TOOL STREQUAL "gcovr") + # Coverage target using gcovr + add_custom_target(coverage + DEPENDS webserv_tests + + # Run tests + COMMAND ${CMAKE_CTEST_COMMAND} --verbose + + # Generate HTML report + COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude-unreachable-branches --exclude '.*test.*' --exclude '.*_deps.*' --html --html-details -o coverage_report.html + + # Generate XML report (for CI) + COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude-unreachable-branches --exclude '.*test.*' --exclude '.*_deps.*' --xml -o coverage.xml + + # Print summary to console + COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude-unreachable-branches --exclude '.*test.*' --exclude '.*_deps.*' + + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Generating code coverage report with gcovr" + ) + + # Coverage summary target + add_custom_target(coverage_summary + DEPENDS webserv_tests + COMMAND ${CMAKE_CTEST_COMMAND} --verbose + COMMAND ${GCOVR_PATH} --root ${CMAKE_SOURCE_DIR} --exclude-unreachable-branches --exclude '.*test.*' --exclude '.*_deps.*' + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Generating coverage summary" + ) + + else() + # Basic coverage target without report generation + add_custom_target(coverage + DEPENDS webserv_tests + COMMAND ${CMAKE_CTEST_COMMAND} --verbose + COMMAND ${CMAKE_COMMAND} -E echo "Coverage data generated. Install lcov or gcovr to generate HTML reports." + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running tests with coverage enabled" + ) + endif() + + # Add coverage dependencies + if(TARGET coverage) + add_dependencies(coverage webserv_tests) + endif() + + message(STATUS "Coverage targets available:") + message(STATUS " make coverage - Run tests and generate coverage report") + message(STATUS " make coverage_summary - Run tests and show coverage summary") + message(STATUS " make coverage_clean - Clean coverage data") +endif() + diff --git a/Makefile b/Makefile index 4c75a4f..44c7f76 100644 --- a/Makefile +++ b/Makefile @@ -86,5 +86,22 @@ test_build: release @echo "Building tests only..." $(CMAKE_BUILD) $(BUILD_DIR) --target webserv_tests +# Coverage targets +coverage: + @echo "Running coverage analysis..." + ./coverage.sh + +coverage_clean: + @echo "Cleaning coverage data..." + rm -rf build_coverage + +# Manual coverage build (advanced users) +coverage_manual: + @echo "Building with coverage manually..." + @mkdir -p build_coverage + cd build_coverage && $(CMAKE) .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=ON -DENABLE_COVERAGE=ON $(CMAKE_FLAGS) + $(CMAKE_BUILD) build_coverage + cd build_coverage && $(CMAKE_BUILD) . --target coverage + # Mark targets as phony -.PHONY: all release debug asan run run_release run_debug run_asan clean fclean re test test_verbose test_build +.PHONY: all release debug asan run run_release run_debug run_asan clean fclean re test test_verbose test_build coverage coverage_clean coverage_manual diff --git a/coverage.sh b/coverage.sh new file mode 100755 index 0000000..c22d0b8 --- /dev/null +++ b/coverage.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +# Webserv Code Coverage Script +# This script builds the project with coverage enabled and generates a coverage report + +set -e # Exit on any error + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$SCRIPT_DIR" +BUILD_DIR="$PROJECT_ROOT/build_coverage" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}Webserv Code Coverage Generator${NC}" +echo "==================================" + +# Check for required tools +echo -e "${YELLOW}Checking for required tools...${NC}" + +# Check for CMake +if ! command -v cmake &> /dev/null; then + echo -e "${RED}Error: CMake not found. Please install CMake.${NC}" + exit 1 +fi + +# Check for compiler +if command -v g++ &> /dev/null; then + COMPILER="g++" + echo -e "${GREEN}Found compiler: $COMPILER${NC}" +elif command -v clang++ &> /dev/null; then + COMPILER="clang++" + echo -e "${GREEN}Found compiler: $COMPILER${NC}" +else + echo -e "${RED}Error: No suitable C++ compiler found (g++ or clang++)${NC}" + exit 1 +fi + +# Check for coverage tools +COVERAGE_TOOL="" +if command -v lcov &> /dev/null && command -v genhtml &> /dev/null; then + COVERAGE_TOOL="lcov" + echo -e "${GREEN}Found coverage tool: lcov${NC}" +elif command -v gcovr &> /dev/null; then + COVERAGE_TOOL="gcovr" + echo -e "${GREEN}Found coverage tool: gcovr${NC}" +else + echo -e "${YELLOW}Warning: No coverage report tools found.${NC}" + echo -e "${YELLOW}Install with: sudo apt-get install lcov${NC}" + echo -e "${YELLOW}Or: pip install gcovr${NC}" + echo -e "${YELLOW}Coverage data will still be generated.${NC}" +fi + +# Clean previous build +echo -e "${YELLOW}Cleaning previous coverage build...${NC}" +rm -rf "$BUILD_DIR" +mkdir -p "$BUILD_DIR" + +# Configure with coverage +echo -e "${YELLOW}Configuring build with coverage enabled...${NC}" +cd "$BUILD_DIR" + +cmake .. \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_TESTS=ON \ + -DENABLE_COVERAGE=ON \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + +# Build +echo -e "${YELLOW}Building project...${NC}" +make -j$(nproc) + +# Run tests with coverage +echo -e "${YELLOW}Running tests with coverage...${NC}" +if make coverage; then + echo -e "${GREEN}Coverage report generated successfully!${NC}" + + # Show where to find the report + if [ "$COVERAGE_TOOL" = "lcov" ]; then + REPORT_PATH="$BUILD_DIR/coverage_report/index.html" + if [ -f "$REPORT_PATH" ]; then + echo -e "${GREEN}Coverage report available at: $REPORT_PATH${NC}" + echo -e "${BLUE}Open with: firefox $REPORT_PATH${NC}" + fi + elif [ "$COVERAGE_TOOL" = "gcovr" ]; then + REPORT_PATH="$BUILD_DIR/coverage_report.html" + if [ -f "$REPORT_PATH" ]; then + echo -e "${GREEN}Coverage report available at: $REPORT_PATH${NC}" + echo -e "${BLUE}Open with: firefox $REPORT_PATH${NC}" + fi + fi + + # Show summary + echo -e "${YELLOW}Coverage Summary:${NC}" + make coverage_summary 2>/dev/null || echo "Summary not available" + +else + echo -e "${RED}Coverage generation failed!${NC}" + exit 1 +fi + +echo -e "${GREEN}Coverage analysis complete!${NC}"