Commit e8494460 authored by Alberto Miranda's avatar Alberto Miranda ♨️ Committed by Marc Vef
Browse files

First implementation of self-tests harness

parent efdce80e
Loading
Loading
Loading
Loading
+172 −0
Original line number Diff line number Diff line
include(CMakeParseArguments)

function(gkfs_enable_python_testing)
    # Parse arguments
    set(MULTI BINARY_DIRECTORIES LIBRARY_PREFIX_DIRECTORIES)

    cmake_parse_arguments(PYTEST "${OPTION}" "${SINGLE}" "${MULTI}" ${ARGN})

    if(PYTEST_UNPARSED_ARGUMENTS)
        message(WARNING "Unparsed arguments in gkfs_enable_python_testing: This often indicates typos!")
    endif()

    if(PYTEST_BINARY_DIRECTORIES)
        set(GKFS_PYTEST_BINARY_DIRECTORIES ${PYTEST_BINARY_DIRECTORIES} PARENT_SCOPE)
    endif()

    if(PYTEST_LIBRARY_PREFIX_DIRECTORIES)
        set(GKFS_PYTEST_LIBRARY_PREFIX_DIRECTORIES ${PYTEST_LIBRARY_PREFIX_DIRECTORIES} PARENT_SCOPE)
    endif()

    set(PYTEST_BINDIR_ARGS, "")
    if(PYTEST_BINARY_DIRECTORIES)
        foreach(dir IN LISTS PYTEST_BINARY_DIRECTORIES)
            list(APPEND PYTEST_BINDIR_ARGS "--bin-dir=${dir}")
        endforeach()
    endif()

    set(PYTEST_LIBDIR_ARGS, "")
    if(PYTEST_LIBRARY_PREFIX_DIRECTORIES)
        foreach(dir IN LISTS PYTEST_LIBRARY_PREFIX_DIRECTORIES)

            if(NOT IS_ABSOLUTE ${dir})
                set(dir ${CMAKE_BINARY_DIR}/${dir})
            endif()

            file(TO_CMAKE_PATH "${dir}/lib" libdir)
            file(TO_CMAKE_PATH "${dir}/lib64" lib64dir)

            if(EXISTS ${libdir})
                list(APPEND PYTEST_LIBDIR_ARGS "--lib-dir=${libdir}")
            endif()

            if(EXISTS ${lib64dir})
                list(APPEND PYTEST_LIBDIR_ARGS "--lib-dir=${lib64dir}")
            endif()
        endforeach()
    endif()

    # convert path lists to space separated arguments
    string(REPLACE ";" " " PYTEST_BINDIR_ARGS "${PYTEST_BINDIR_ARGS}")
    string(REPLACE ";" " " PYTEST_BINDIR_ARGS "${PYTEST_BINDIR_ARGS}")

    configure_file(pytest.ini.in pytest.ini @ONLY)
    configure_file(conftest.py.in conftest.py @ONLY)
    configure_file(harness/cli.py harness/cli.py COPYONLY)

    # enable testing
    set(GKFS_PYTHON_TESTING_ENABLED ON PARENT_SCOPE)

endfunction()

function(gkfs_add_python_test)
    # ignore call if testing is not enabled
    if(NOT CMAKE_TESTING_ENABLED OR NOT GKFS_PYTHON_TESTING_ENABLED)
        return()
    endif()

    # Parse arguments
    set(OPTION)
    set(SINGLE NAME PYTHON_VERSION WORKING_DIRECTORY VIRTUALENV)
    set(MULTI COMMAND BINARY_DIRECTORIES LIBRARY_PREFIX_DIRECTORIES)

    cmake_parse_arguments(PYTEST "${OPTION}" "${SINGLE}" "${MULTI}" ${ARGN})

    if(PYTEST_UNPARSED_ARGUMENTS)
        message(WARNING "Unparsed arguments in gkfs_add_python_test: This often indicates typos!")
    endif()

    if(NOT PYTEST_NAME)
        message(FATAL_ERROR "gkfs_add_python_test requires a NAME argument")
    endif()

    # set default values for arguments not provided
    if(NOT PYTEST_PYTHON_VERSION)
        set(PYTEST_PYTHON_VERSION 3.0)
    endif()

    if(NOT PYTEST_WORKING_DIRECTORY)
        set(PYTEST_WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
    endif()

    if(NOT PYTEST_VIRTUALENV)
        set(PYTEST_VIRTUALENV ${CMAKE_CURRENT_BINARY_DIR}/pytest-venv)
    endif()

    # if the test doesn't provide a list of binary or library prefix 
    # directories, use the one set on gkfs_enable_python_testing()
    if(NOT PYTEST_BINARY_DIRECTORIES)
        set(PYTEST_BINARY_DIRECTORIES ${GKFS_PYTEST_BINARY_DIRECTORIES})
    endif()

    if(NOT PYTEST_LIBRARY_PREFIX_DIRECTORIES)
        set(PYTEST_LIBRARY_PREFIX_DIRECTORIES ${GKFS_PYTEST_LIBRARY_PREFIX_DIRECTORIES})
    endif()

    set(PYTEST_COMMAND_ARGS, "")
    if(PYTEST_BINARY_DIRECTORIES)
        foreach(dir IN LISTS PYTEST_BINARY_DIRECTORIES)
            list(APPEND PYTEST_COMMAND_ARGS "--bin-dir=${dir}")
        endforeach()
    endif()

    if(PYTEST_LIBRARY_PREFIX_DIRECTORIES)
        foreach(dir IN LISTS PYTEST_LIBRARY_PREFIX_DIRECTORIES)

            if(NOT IS_ABSOLUTE ${dir})
                set(dir ${CMAKE_BINARY_DIR}/${dir})
            endif()

            file(TO_CMAKE_PATH "${dir}/lib" libdir)
            file(TO_CMAKE_PATH "${dir}/lib64" lib64dir)

            if(EXISTS "${dir}/lib")
                list(APPEND PYTEST_COMMAND_ARGS "--lib-dir=${libdir}")
            endif()

            if(EXISTS "${dir}/lib64")
                list(APPEND PYTEST_COMMAND_ARGS "--lib-dir=${lib64dir}")
            endif()
        endforeach()
    endif()

    # Extend the given virtualenv to be a full path.
    if(NOT IS_ABSOLUTE ${PYTEST_VIRTUALENV})
        set(PYTEST_VIRTUALENV ${CMAKE_BINARY_DIR}/${PYTEST_VIRTUALENV})
    endif()

    # find an appropriate python interpreter
    find_package(Python3 
        ${PYTEST_PYTHON_VERSION} 
        REQUIRED 
        COMPONENTS Interpreter)

    set(PYTEST_VIRTUALENV_PIP ${PYTEST_VIRTUALENV}/bin/pip)
    set(PYTEST_VIRTUALENV_INTERPRETER ${PYTEST_VIRTUALENV}/bin/python)

    # create a virtual environment to run the test
    configure_file(requirements.txt.in requirements.txt @ONLY)

    add_custom_command(
        OUTPUT ${PYTEST_VIRTUALENV}
        COMMENT "Creating virtual environment ${PYTEST_VIRTUALENV}"
        COMMAND Python3::Interpreter -m venv "${PYTEST_VIRTUALENV}"
        COMMAND ${PYTEST_VIRTUALENV_PIP} install --upgrade pip -q
        COMMAND ${PYTEST_VIRTUALENV_PIP} install -r requirements.txt --upgrade -q
    )

    # ensure that the virtual environment is created by the build process
    # (this is required because we can't add dependencies between 
    # "test targets" and "normal targets"
    add_custom_target(venv 
        ALL
        DEPENDS ${PYTEST_VIRTUALENV}
        DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/requirements.txt)

    add_test(NAME ${PYTEST_NAME}
             COMMAND ${PYTEST_VIRTUALENV_INTERPRETER} 
                    -m pytest 
                    ${PYTEST_COMMAND_ARGS}
                    ${PYTEST_COMMAND}
             WORKING_DIRECTORY ${PYTEST_WORKING_DIRECTORY})
endfunction()
+8 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ project(
    VERSION 0.7.0
)

enable_testing()

if(NOT CMAKE_COMPILER_IS_GNUCC)
    message(FATAL_ERROR "The choosen C compiler is not gcc and is not supported")
@@ -209,3 +210,10 @@ add_subdirectory(src/global)
add_subdirectory(src/daemon)
# Client library
add_subdirectory(src/client)

option(GKFS_BUILD_TESTS "Build GekkoFS self tests" ON)

if(GKFS_BUILD_TESTS)
    message(STATUS "[gekkofs] Preparing tests...")
    add_subdirectory(tests)
endif()

tests/CMakeLists.txt

0 → 100644
+25 −0
Original line number Diff line number Diff line
include(GkfsPythonTesting)

# ensure helper programs in the testing harness get built
add_subdirectory(harness)

gkfs_enable_python_testing(
    BINARY_DIRECTORIES ${CMAKE_BINARY_DIR}/src/daemon/
                       ${CMAKE_BINARY_DIR}/src/client/
                       ${CMAKE_BINARY_DIR}/tests/harness/
    LIBRARY_PREFIX_DIRECTORIES ${CMAKE_PREFIX_PATH}
)

add_custom_target(check
    COMMAND ${CMAKE_CTEST_COMMAND}
            --force-new-ctest-process
            --verbose
            --output-on-failure
)

gkfs_add_python_test(
    NAME test_directories
    PYTHON_VERSION 3.7
    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
    COMMAND tests/test_directories.py
)

tests/addoptions.py.in

0 → 100644
+32 −0
Original line number Diff line number Diff line
### This code is meant to be included automatically by CMake in the build 
### directory's top-level conftest.py as well as the source directory's 
### conftest.py, so that tests can be correctly run from both directories
def pytest_addoption(parser):
    """
    Adds extra options to the py.test CLI so that we can provide
    search directories for libraries and helper programs.
    """

    parser.addoption(
        '--interface',
        action='store',
        type=str,
        default='lo',
        help="network interface used for communications (default: 'lo')."
    )

    parser.addoption(
        "--bin-dir",
        action='append',
        default=[Path.cwd()],
        help="directory that should be considered when searching "
             "for programs (multi-allowed)."
    )

    parser.addoption(
        "--lib-dir",
        action='append',
        default=[Path.cwd()],
        help="directory that should be considered when searching "
             "for libraries (multi-allowed)."
    )

tests/conftest.py

0 → 100644
+64 −0
Original line number Diff line number Diff line
import os, sys
import pytest
import logging
from _pytest.logging import caplog as _caplog
from loguru import logger
from pathlib import Path
from harness.cli import add_cli_options
from harness.workspace import Workspace
from harness.gkfs import Daemon, Client

def pytest_addoption(parser):
    """
    Adds extra options from the GKFS harness to the py.test CLI.
    """
    add_cli_options(parser)

@pytest.fixture(autouse=True)
def caplog(_caplog):

    _caplog.set_level(logging.CRITICAL, 'sh.command')
    _caplog.set_level(logging.CRITICAL, 'sh.command.process')
    _caplog.set_level(logging.CRITICAL, 'sh.command.process.streamreader')
    _caplog.set_level(logging.CRITICAL, 'sh.stream_bufferer')
    _caplog.set_level(logging.CRITICAL, 'sh.streamreader')

    class PropagateHandler(logging.Handler):
        def emit(self, record):
            logging.getLogger(record.name).handle(record)

    handler_id = logger.add(PropagateHandler(), format="{message}")
    yield _caplog
    logger.remove(handler_id)

@pytest.fixture
def test_workspace(tmp_path, request):
    """
    Initializes a test workspace by creating a temporary directory for it.
    """

    return Workspace(tmp_path,
            request.config.getoption('--bin-dir'),
            request.config.getoption('--lib-dir'))

@pytest.fixture
def gkfs_daemon(test_workspace, request):
    """
    Initializes a local gekkofs daemon
    """

    interface = request.config.getoption('--interface')

    daemon = Daemon(interface, test_workspace)
    yield daemon.run()
    daemon.shutdown()

@pytest.fixture
def gkfs_client(test_workspace):
    """
    Sets up a gekkofs client environment so that
    operations (system calls, library calls, ...) can 
    be requested from a co-running daemon.
    """

    return Client(test_workspace)
Loading