diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 2d6d297..63c14d2 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: env: - CMLIB_VERSION: 1.1.0 + CMLIB_VERSION: 1.2.1 jobs: examples-linux: @@ -36,8 +36,10 @@ jobs: git remote set-url origin "${{ github.server_url }}/${{ github.repository }}" git clone --branch "v${CMLIB_VERSION}" https://github.com/cmakelib/cmakelib.git export CMLIB_DIR=$(pwd)/cmakelib + export CMLIB_COMPONENT_LOCAL_BASE_PATH=$(pwd) + ln -s ./ cmakelib-component-cmconf cd ${{ matrix.example }} - cmake -P config/CMCONF_EXAMPLEConfig.cmake + cmake -DCMCONF_INSTALL_AS_SYMLINK=ON -P config/CMCONF_EXAMPLEConfig.cmake cmake -P CMakeLists.txt mkdir _build && cd _build cmake .. @@ -59,8 +61,10 @@ jobs: git remote set-url origin "${{ github.server_url }}/${{ github.repository }}" git clone --branch "v$env:CMLIB_VERSION" https://github.com/cmakelib/cmakelib.git $env:CMLIB_DIR = "$(pwd)/cmakelib" + $env:CMLIB_COMPONENT_LOCAL_BASE_PATH = "$(pwd)" + New-Item -ItemType SymbolicLink -Path "cmakelib-component-cmconf" -Target "./" cd ${{ matrix.example }} - cmake -P config/CMCONF_EXAMPLEConfig.cmake + cmake -DCMCONF_INSTALL_AS_SYMLINK=ON -P config/CMCONF_EXAMPLEConfig.cmake cmake -P CMakeLists.txt mkdir build -ErrorAction SilentlyContinue cd build @@ -83,8 +87,10 @@ jobs: git remote set-url origin "${{ github.server_url }}/${{ github.repository }}" git clone --branch "v${CMLIB_VERSION}" https://github.com/cmakelib/cmakelib.git export CMLIB_DIR=$(pwd)/cmakelib + export CMLIB_COMPONENT_LOCAL_BASE_PATH=$(pwd) + ln -s ./ cmakelib-component-cmconf cd ${{ matrix.example }} - cmake -P config/CMCONF_EXAMPLEConfig.cmake + cmake -DCMCONF_INSTALL_AS_SYMLINK=ON -P config/CMCONF_EXAMPLEConfig.cmake cmake -P CMakeLists.txt mkdir _build && cd _build cmake .. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b67ea3d..0630ade 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: env: - CMLIB_VERSION: 1.1.0 + CMLIB_VERSION: 1.2.1 jobs: test_linux: @@ -40,6 +40,8 @@ jobs: git remote set-url origin "${{ github.server_url }}/${{ github.repository }}" git clone --branch "v${CMLIB_VERSION}" https://github.com/cmakelib/cmakelib.git export CMLIB_DIR=$(pwd)/cmakelib + export CMLIB_COMPONENT_LOCAL_BASE_PATH=$(pwd)/../ + ls -lsa cd test/ && cmake . test_macos: @@ -54,6 +56,7 @@ jobs: git remote set-url origin "${{ github.server_url }}/${{ github.repository }}" git clone --branch "v${CMLIB_VERSION}" https://github.com/cmakelib/cmakelib.git export CMLIB_DIR=$(pwd)/cmakelib + export CMLIB_COMPONENT_LOCAL_BASE_PATH=$(pwd)/../ cd test/ && cmake . test_windows: @@ -68,4 +71,5 @@ jobs: git remote set-url origin "${{ github.server_url }}/${{ github.repository }}" git clone --branch "v$Env:CMLIB_VERSION" https://github.com/cmakelib/cmakelib.git $Env:CMLIB_DIR=$(Join-Path -Path $(Get-Location).Path -ChildPath "cmakelib") + $Env:CMLIB_COMPONENT_LOCAL_BASE_PATH=$(Join-Path -Path $(Get-Location).Path -ChildPath "../") cd test/ && cmake . \ No newline at end of file diff --git a/CMCONF.cmake b/CMCONF.cmake index 8ea5e16..0eb29b6 100644 --- a/CMCONF.cmake +++ b/CMCONF.cmake @@ -411,12 +411,30 @@ FUNCTION(_CMCONF_UNINSTALL system_name) SET(package_registry_path) IF(WIN32) - SET(userprofile "$ENV{USERPROFILE}") - IF(NOT userprofile) - _CMCONF_MESSAGE(FATAL_ERROR "Cannot uninstall configuration. USERPROFILE environment variable is not set.") + SET(winreg_path "HKCU\\Software\\Kitware\\CMake\\Packages\\${package_name}") + FIND_PROGRAM(reg_exe reg) + IF(NOT reg_exe) + _CMCONF_MESSAGE(FATAL_ERROR "Cannot uninstall configuration. 'reg' executable not found.") + ENDIF() + EXECUTE_PROCESS( + COMMAND "${reg_exe}" query "${winreg_path}" + RESULT_VARIABLE winreg_exist + ERROR_VARIABLE errout + OUTPUT_VARIABLE stdout + ) + IF(NOT winreg_exist EQUAL 0) + _CMCONF_MESSAGE(WARNING "No need to uninstall configuration for ${system_name}. It is not installed.") + RETURN() + ENDIF() + EXECUTE_PROCESS( + COMMAND "${reg_exe}" delete "${winreg_path}" /f + RESULT_VARIABLE result_var + ERROR_VARIABLE errout + OUTPUT_VARIABLE stdout + ) + IF(NOT result_var EQUAL 0) + _CMCONF_MESSAGE(FATAL_ERROR "Failed to uninstall configuration for system ${system_name}: ${errout}\n${stdout}") ENDIF() - FILE(TO_CMAKE_PATH "${userprofile}" userprofile) - SET(package_registry_path "${userprofile}/.cmake/packages/${package_name}") ELSEIF(UNIX) SET(userhome "$ENV{HOME}") IF(NOT userhome) @@ -424,15 +442,17 @@ FUNCTION(_CMCONF_UNINSTALL system_name) ENDIF() FILE(TO_CMAKE_PATH "${userhome}" userhome) SET(package_registry_path "${userhome}/.cmake/packages/${package_name}") - ENDIF() - FIND_PACKAGE(${package_name} QUIET) - IF(NOT ${package_name}_FOUND) - _CMCONF_MESSAGE(WARNING "No need to uninstall configuration for ${system_name}. It is not installed.") - RETURN() + FIND_PACKAGE(${package_name} QUIET) + IF(NOT ${package_name}_FOUND) + _CMCONF_MESSAGE(WARNING "No need to uninstall configuration for ${system_name}. It is not installed.") + RETURN() + ENDIF() + FILE(REMOVE_RECURSE "${package_registry_path}") + ELSE() + _CMCONF_MESSAGE(FATAL_ERROR "Cannot uninstall configuration. Unsupported platform.") ENDIF() - FILE(REMOVE_RECURSE "${package_registry_path}") _CMCONF_MESSAGE(STATUS "Uninstalled configuration for system ${system_name} succeeded.") ENDFUNCTION() diff --git a/README.md b/README.md index df30291..f6a18d0 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,10 @@ Every function has comprehensive documentation written as part of the function d Context documentation is located at [CMCONF.cmake] +## Tests + +For test go to the [test/] and read the attached [test/README.md]. + ## License Project is licensed under [MIT](LICENSE) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 09977c9..44dfbe2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,6 +19,7 @@ TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/pass/cache_variable_behavior") TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/pass/case_insensitive") TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/pass/different_system_names") TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/pass/multiple_variables") +TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/pass/uninstall_verification") TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/fail/config_not_installed") TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/fail/config_already_installed") @@ -38,10 +39,6 @@ TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/fail/variable_already_exists") TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/fail/variable_not_defined") TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/fail/wrong_filename") -IF(WIN32) - TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/fail/windows_userprofile_missing") -ENDIF() - IF(UNIX) TEST_RUN("${CMAKE_CURRENT_LIST_DIR}/test_cases/fail/unix_home_missing") ENDIF() diff --git a/test/README.md b/test/README.md index e69de29..030ab57 100644 --- a/test/README.md +++ b/test/README.md @@ -0,0 +1,81 @@ +# CMCONF Test Suite + +Comprehensive test suite for the CMCONF (CMake Configuration Framework) component library. + +## Test Modules + +Currently tested functionality + +- **`CMCONF_SET`** - Configuration variable setting +- **`CMCONF_GET`** - Configuration variable retrieval +- **`CMCONF_INIT_SYSTEM`** - System name configuration +- **`INSTALL Feature`** - Configuration installation +- **`UNINSTALL Feature`** - Configuration uninstallation + +Not covered functionality + +- Edge cases for Windows OS regard of `reg` command behaviour. + It is expected that this command is available on Windows be default. + If not, not only CMake but also Windows itself is broken. + +### Test Structure + +Tests are organized into subdirectories within `test_cases`, separated into: + +- **`pass/`** - Tests that verify correct functionality and expected behavior +- **`fail/`** - Tests that verify proper error handling and validation + +Each test case directory contains a `CMakeLists.txt` file that tests specific functionality. + +Configuration templates and test resources are placed in the `config_templates` directory. + +## Test Framework + +- `TEST.cmake` - Common test macros. It is reused from CMLIB. +- `cache_var.cmake` - Macros to force set and restore cache variables. It is reused from CMLIB. +- `test_cmconf_helpers.cmake` - CMCONF-specific test helper functions and macros + +### Test Resource Creation and Maintenance + +When external resources are needed to test functionality: + +- Configuration templates are provided in the `config_templates` directory +- Test configurations are created from predefined templates +- There are no external dependencies to download or install +- There shall be no dynamic creation of test resources during a test run (exceptions can apply if reasoned) + +## Running Tests + +**All tests are designed to be run from a clean source tree.** + +CMCONF is consistent and functional only when all tests pass. + +Any test failure indicates CMCONF is not working as expected, even if the failure seems unrelated to required functionality. Complete system consistency is essential for reliable operation. + +### Run All Tests + +Tests shall be run in Project and Script mode. + +```bash +# Project mode +git clean -xfd . +cmake . + +# Script mode +git clean -xfd . +cmake -P ./test/CMakeLists.txt +``` + +### Clean Up + +```bash +git clean -xfd . +``` + +## Platform Considerations + +Tests are designed to run on Linux-based systems as the main development platform. Platform-specific behavior is tested where applicable, particularly for: + +- Unix home directory handling +- System-specific configuration paths (windows vs unix) +- Platform-specific variable behaviors \ No newline at end of file diff --git a/test/test_cases/fail/unix_home_missing/CMakeLists.txt b/test/test_cases/fail/unix_home_missing/CMakeLists.txt index ad081d3..b0ca8c7 100644 --- a/test/test_cases/fail/unix_home_missing/CMakeLists.txt +++ b/test/test_cases/fail/unix_home_missing/CMakeLists.txt @@ -15,7 +15,18 @@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../test_cmconf_helpers.cmake") INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../config_templates/config_template.cmake") TEST_CMCONF_CHECK_AND_INSTALL_CONFIG("${CMCONF_TEST_CONFIG_FILE}" "TEST") -TEST_RUN_AND_CHECK_OUTPUT("test_config" - FATAL_ERROR_MESSAGE "CMCONF\\\\[TEST\\\\] - Cannot uninstall configuration\\..*HOME environment variable.*is not set\\." + +EXECUTE_PROCESS( + COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_LIST_DIR}/test_config/CMakeLists.txt" + RESULT_VARIABLE result + ERROR_VARIABLE error_output + OUTPUT_VARIABLE output ) +IF(result EQUAL 0) + MESSAGE(FATAL_ERROR "Expected test to fail but it succeeded") +ENDIF() +IF(NOT error_output MATCHES "CMCONF\\[TEST\\] - Cannot uninstall configuration\\..*HOME environment variable.*is not set\\.") + MESSAGE(FATAL_ERROR "Expected error message not found in output: ${error_output}") +ENDIF() + TEST_CMCONF_UNINSTALL_CONFIG("${CMCONF_TEST_CONFIG_FILE}" "TEST") diff --git a/test/test_cases/fail/windows_userprofile_missing/CMakeLists.txt b/test/test_cases/fail/windows_userprofile_missing/CMakeLists.txt deleted file mode 100644 index 313c8a4..0000000 --- a/test/test_cases/fail/windows_userprofile_missing/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -## Main -# -# Test CMCONF_INIT_SYSTEM error when USERPROFILE is not set on Windows -# - -IF(NOT DEFINED CMAKE_SCRIPT_MODE_FILE) - CMAKE_MINIMUM_REQUIRED(VERSION 3.22) - PROJECT(CMCONF_WINDOWS_USERPROFILE_MISSING_TEST) -ENDIF() - -FIND_PACKAGE(CMLIB REQUIRED) - -INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../TEST.cmake") -INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../test_cmconf_helpers.cmake") -INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../config_templates/config_template.cmake") - -TEST_CMCONF_CHECK_AND_INSTALL_CONFIG("${CMCONF_TEST_CONFIG_FILE}" "TEST") -TEST_RUN_AND_CHECK_OUTPUT("test_config" - FATAL_ERROR_MESSAGE "CMCONF\\\\[TEST\\\\] - Cannot uninstall configuration\\. USERPROFILE environment variable is not set\\." -) -TEST_CMCONF_UNINSTALL_CONFIG("${CMCONF_TEST_CONFIG_FILE}" "TEST") diff --git a/test/test_cases/fail/windows_userprofile_missing/test_config/CMakeLists.txt b/test/test_cases/fail/windows_userprofile_missing/test_config/CMakeLists.txt deleted file mode 100644 index a09c56a..0000000 --- a/test/test_cases/fail/windows_userprofile_missing/test_config/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -## Test configuration that unsets USERPROFILE on Windows -# -# This should generate a fatal error when CMCONF_INIT_SYSTEM is called with UNINSTALL=ON -# - -IF(NOT DEFINED CMAKE_SCRIPT_MODE_FILE) - CMAKE_MINIMUM_REQUIRED(VERSION 3.22) - PROJECT(CMCONF_WINDOWS_USERPROFILE_MISSING_TEST_CONFIG) -ENDIF() - -FIND_PACKAGE(CMLIB REQUIRED) - -INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../../../CMCONF.cmake") - -SET(ENV{USERPROFILE} "") -SET(CMCONF_UNINSTALL ON CACHE BOOL "" FORCE) -CMCONF_INIT_SYSTEM("TEST") diff --git a/test/test_cases/pass/uninstall_verification/CMakeLists.txt b/test/test_cases/pass/uninstall_verification/CMakeLists.txt new file mode 100644 index 0000000..0c0c355 --- /dev/null +++ b/test/test_cases/pass/uninstall_verification/CMakeLists.txt @@ -0,0 +1,45 @@ +## Main +# +# Test that CMCONF uninstallation actually removes the package from registry +# +# Strategy: Single file for sequential state-dependent test +# Steps must execute in exact order with shared state. +# Splitting would require complex state passing and risk inconsistent cleanup on failures. +# + +IF(NOT DEFINED CMAKE_SCRIPT_MODE_FILE) + CMAKE_MINIMUM_REQUIRED(VERSION 3.22) + PROJECT(CMCONF_UNINSTALL_VERIFICATION_TEST) +ENDIF() + +FIND_PACKAGE(CMLIB REQUIRED) + +INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../TEST.cmake") +INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../cache_var.cmake") +INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../../CMCONF.cmake") +INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../test_cmconf_helpers.cmake") +INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../../../config_templates/config_template.cmake") + +TEST_CMCONF_CHECK_AND_INSTALL_CONFIG("${CMCONF_TEST_CONFIG_FILE}" "TEST") + +SET(package_name "CMCONF_TEST") +FIND_PACKAGE(${package_name} QUIET) +IF(NOT ${package_name}_FOUND) + MESSAGE(FATAL_ERROR "Package ${package_name} should be found after installation, but it was not found") +ENDIF() + +CMCONF_INIT_SYSTEM(TEST) +CMCONF_GET(VARIABLE_A) +TEST_VAR_DEFINED(VARIABLE_A) +TEST_VAR_EQUALS_LITERAL(VARIABLE_A "test_value_a") + +TEST_CMCONF_UNINSTALL_CONFIG("${CMCONF_TEST_CONFIG_FILE}" "TEST") + +UNSET(${package_name}_FOUND CACHE) +UNSET(${package_name}_DIR CACHE) +UNSET(${package_name}_CONFIG CACHE) + +FIND_PACKAGE(${package_name} QUIET) +IF(${package_name}_FOUND) + MESSAGE(FATAL_ERROR "Package ${package_name} should NOT be found after uninstallation, but it was still found at: ${${package_name}_DIR}") +ENDIF()