Skip to content

Commit

Permalink
Merge pull request #8 from tipi-build/feature/testing
Browse files Browse the repository at this point in the history
Test suite for goldilock
  • Loading branch information
pysco68 authored Jan 9, 2025
2 parents 1f59315 + ee5c668 commit 55c00bd
Show file tree
Hide file tree
Showing 26 changed files with 2,172 additions and 95 deletions.
59 changes: 55 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ jobs:
asset_path: ./build/macos/goldilock.zip
asset_name: goldilock-macos.zip
asset_content_type: application/zip
- name: Run tests
run: |
cd build/macos/
ctest --verbose -E test_docker
build-macos-intel:
name: build-macos-intel
Expand All @@ -64,7 +68,8 @@ jobs:
run: |
cmake -S . -B build/macos -DCMAKE_TOOLCHAIN_FILE=environments/macos-clang-cxx17.cmake -DCMAKE_BUILD_TYPE=Release
cmake --build build/macos
cd build/macos/ && cpack -G ZIP
cd build/macos/
cpack -G ZIP
- name: Upload goldilock package
if: ${{needs.draft-release.outputs.upload_url}}
id: upload-tipi-goldilock-package
Expand All @@ -76,20 +81,45 @@ jobs:
asset_path: ./build/macos/goldilock.zip
asset_name: goldilock-macos-intel.zip
asset_content_type: application/zip
- name: Run tests
run: |
cd build/macos/
ctest --verbose -E test_docker
build-linux:
name: build-linux
runs-on: ubuntu-latest
container:
image: tipibuild/tipi-ubuntu:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# --init makes sure the container has an init process running as as the PID 1 in the container. Specifying an init process ensures the usual responsibilities of an init system, such as reaping zombie processes, are performed inside the created container.
options: --init
needs: draft-release
steps:
- uses: actions/checkout@v2
- name: setup
run: |
sudo apt update
sudo apt-get install -y docker.io
# the buildx plugin is available under either one of these names
set +e
sudo apt-get install -y docker-buildx
sudo apt-get install -y docker-buildx-plugin
set -e
sudo docker --version
sudo docker buildx version
- name: install and build
run: |
tipi run cmake -S . -B build/linux -DCMAKE_TOOLCHAIN_FILE=environments/linux-clang-cxx17-static.cmake -DCMAKE_BUILD_TYPE=Release
tipi run cmake -S . -B build/linux -GNinja -DCMAKE_TOOLCHAIN_FILE=environments/linux-clang-cxx17-static.cmake -DCMAKE_BUILD_TYPE=Release
tipi run cmake --build build/linux
cd build/linux/ && tipi run cpack -G ZIP
cd build/linux/
tipi run cpack -G ZIP
- name: Upload goldilock package
if: ${{needs.draft-release.outputs.upload_url}}
id: upload-tipi-goldilock-package
Expand All @@ -100,4 +130,25 @@ jobs:
upload_url: ${{needs.draft-release.outputs.upload_url}}
asset_path: ./build/linux/goldilock.zip
asset_name: goldilock-linux.zip
asset_content_type: application/zip
asset_content_type: application/zip
- name: Run tests
run: |
cd build/linux/
tipi run ctest --verbose -E test_docker
- name: Run docker tests
run: |
# we will use this to be able to map the docker-in-docker paths between
# this environment and the containers the tests will run
export SHARE_VOLUME_FOLDER=_dind_shared_volume
export GOLDILOCK_TEST_DIND_SHARED_VOLUME_CONTAINER=${{ github.workspace }}/$SHARE_VOLUME_FOLDER
export GOLDILOCK_TEST_DIND_SHARED_VOLUME_HOST=${{ github.workspace }}/$SHARE_VOLUME_FOLDER
export GOLDILOCK_TEST_DIND_SHARED_VOLUME_TEST=$(pwd)/$SHARE_VOLUME_FOLDER
# create the folder from the test environmnent's perspective:
mkdir -p $GOLDILOCK_TEST_DIND_SHARED_VOLUME_TEST
echo "hello" > $GOLDILOCK_TEST_DIND_SHARED_VOLUME_TEST/test_from_ci_yaml
cd build/linux/
# ... we need priviledge to write to the docker socket
sudo --preserve-env tipi run ctest --verbose -R test_docker
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ project(goldilock
VERSION "1.1.0"
LANGUAGES CXX
)

enable_testing()

set(CMAKE_CXX_STANDARD 17)
Expand All @@ -26,7 +27,7 @@ FetchContent_Declare(
)

FetchContent_MakeAvailable(cxxopts)
set(BOOST_INCLUDE_LIBRARIES system filesystem regex lexical_cast process scope_exit uuid interprocess serialization)
set(BOOST_INCLUDE_LIBRARIES system filesystem regex lexical_cast process scope_exit uuid interprocess serialization exception test)
FetchContent_Declare(
boost
GIT_REPOSITORY https://github.com/tipi-build/boost
Expand All @@ -45,4 +46,5 @@ target_include_directories(libgoldilock-utils INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
)

add_subdirectory(src)
add_subdirectory(src)
add_subdirectory(test)
2 changes: 1 addition & 1 deletion cmake/gen_version.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ find_package(Git)

if(GIT_EXECUTABLE)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags --always --dirty
COMMAND ${GIT_EXECUTABLE} describe --always --dirty
WORKING_DIRECTORY ${WORKING_DIR}
OUTPUT_VARIABLE GOLDILOCK_GIT_REVISION
RESULT_VARIABLE ERROR_CODE
Expand Down
5 changes: 5 additions & 0 deletions environments/flags/vs-cxx17.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj /EHsc /std:c++17" CACHE STRING "CXX_FLAGS" FORCE)
set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ Standard (toolchain)" FORCE)
set(CMAKE_CXX_STANDARD_REQUIRED YES CACHE BOOL "C++ Standard required" FORCE)
set(CMAKE_CXX_EXTENSIONS NO CACHE BOOL "C++ Standard extensions" FORCE)
4 changes: 1 addition & 3 deletions environments/linux-clang-cxx17-static.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@ include(${CMAKE_CURRENT_LIST_DIR}/compiler/clang.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/flags/cxx17.cmake)

set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -fuse-ld=lld -stdlib=libc++ -static-libstdc++ -static-libgcc /usr/local/share/.tipi/clang/4f846ee/lib/libc++.a /usr/local/share/.tipi/clang/4f846ee/lib/libc++abi.a")

set (CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -fuse-ld=lld -stdlib=libc++ -static-libstdc++ -static-libgcc /usr/local/share/.tipi/clang/4f846ee/lib/libc++.a /usr/local/share/.tipi/clang/4f846ee/lib/libc++abi.a")
22 changes: 22 additions & 0 deletions environments/macos-clang-cxx17-AddressSanitizer.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
include_guard()

include(${CMAKE_CURRENT_LIST_DIR}/compiler/clang.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/flags/cxx17.cmake)

add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-fsanitize=address>)
add_link_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-fsanitize=address>
$<$<COMPILE_LANGUAGE:C,CXX>:-static-libsan>)
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-uninitialized>)

add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-string-concatenation>)
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shift-overflow>)
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-misleading-indentation>)


set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
9 changes: 9 additions & 0 deletions environments/windows-msvc-cxx17.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
include_guard()

include(${CMAKE_CURRENT_LIST_DIR}/flags/vs-cxx17.cmake)
add_compile_definitions(
# Prevents Windows.h from adding unnecessary includes
WIN32_LEAN_AND_MEAN
# Prevents Windows.h from defining min/max as macros
NOMINMAX
)
27 changes: 27 additions & 0 deletions include/goldilock/file.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

#pragma once

#include <boost/predef.h>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <boost/filesystem.hpp>

namespace tipi::goldilock::file {
using namespace std::string_literals;
namespace fs = boost::filesystem;

inline std::string read_file_content(const char *filename) {
std::ifstream t(filename, std::ios_base::binary);
Expand All @@ -32,4 +35,28 @@ namespace tipi::goldilock::file {
return read_file_content(filename.generic_string());
}

inline void touch_file(const boost::filesystem::path& path) {
std::fstream ofs(path.generic_string(), std::ios::out | std::ios::trunc | std::ios::in | std::ios::binary);
ofs.close();
}

//!\brief same as touch_file() except the files get chmod-ed 666 so that multiple users can share the file
inline void touch_file_permissive(const boost::filesystem::path& path) {
bool set_perms = !fs::exists(path);

// by testing if we can trunc it we'll know if we can write to it...
std::fstream ofs(path.generic_string(), std::ios::out | std::ios::trunc | std::ios::in | std::ios::binary);

if(set_perms) {
try {
fs::permissions(path, fs::add_perms|fs::owner_write|fs::group_write|fs::others_write);
}
catch(...) {
// we *might* get to the seldom case that someones else (re)created the file in beween us touching it and
// thus has the ownership.
}
}

ofs.close();
}
}
10 changes: 6 additions & 4 deletions include/goldilock/fstream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,24 @@

namespace tipi::goldilock::exclusive_fstream
{
namespace fs = boost::filesystem;

struct FILE_closer {
void operator()(std::FILE *fp) const { std::fclose(fp); }
};

inline std::fstream open(const char *filename, const std::string mode = "wx")
inline std::fstream open(const char *filename, const std::string mode = "w")
{
bool excl = [filename, mode] {
std::unique_ptr<std::FILE, FILE_closer> fp(std::fopen(filename, mode.data()));
std::unique_ptr<std::FILE, FILE_closer> fp(std::fopen(filename, mode.data()));
return !!fp;
}();
auto saveerr = errno;

std::fstream stream;

if (excl) {
fs::permissions(filename, fs::add_perms|fs::owner_write|fs::group_write|fs::others_write);
stream.open(filename);
}
else {
Expand All @@ -40,11 +42,11 @@ namespace tipi::goldilock::exclusive_fstream
return stream;
}

inline std::fstream open(const std::string& filename, const std::string mode = "wx") {
inline std::fstream open(const std::string& filename, const std::string mode = "w") {
return open(filename.data(), mode);
}

inline std::fstream open(const boost::filesystem::path& filename, const std::string mode = "wx") {
inline std::fstream open(const boost::filesystem::path& filename, const std::string mode = "w") {
return open(filename.generic_string(), mode);
}
}
45 changes: 34 additions & 11 deletions include/goldilock/goldilock_spot.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ namespace tipi::goldilock {
fs::path spot_path = get_spot_path();

{
auto lockfile_stream = exclusive_fstream::open(spot_path, "wx");
auto lockfile_stream = exclusive_fstream::open(spot_path, "w");
if(lockfile_stream.is_open()) {
boost::archive::text_oarchive oa(lockfile_stream);
oa << *this;
Expand All @@ -126,8 +126,8 @@ namespace tipi::goldilock {

// now read and see if the contents are as expected
{
auto read_back = goldilock_spot::read_from(spot_path, lockfile_);
got_spot = (read_back.get_guid() == get_guid() && read_back.get_timestamp() == get_timestamp());
auto read_back = goldilock_spot::try_read_from(spot_path, lockfile_);
got_spot = (read_back.has_value() && read_back->get_guid() == get_guid() && read_back->get_timestamp() == get_timestamp());
}

if(got_spot) {
Expand Down Expand Up @@ -172,6 +172,17 @@ namespace tipi::goldilock {
return result;
}

static std::optional<goldilock_spot> try_read_from(const fs::path& spot_on_disk, const fs::path& lockfile_path) {
try {
return read_from(spot_on_disk, lockfile_path);
}
catch(...) {
//
}

return std::nullopt;
}

bool is_first_in_line() const {
// try to get to own the spot
auto spots = list_lockfile_spots(lockfile_);
Expand Down Expand Up @@ -280,18 +291,30 @@ namespace tipi::goldilock {
if(locker_in_line.has_value()) {

bool delete_spot = false;
bool read_success = false;

size_t read_retries = 0;
const size_t max_read_retries = 50;

try {
auto spot = goldilock_spot::read_from(directory_entry.path(), lockfile_path);
delete_spot = spot.is_expired();
while(!read_success && read_retries++ < max_read_retries) {

if(!delete_spot) {
result.insert({ directory_entry.path(), spot });
try {
auto spot = goldilock_spot::read_from(directory_entry.path(), lockfile_path);
read_success = true;
delete_spot = spot.is_expired();

if(!delete_spot) {
result.insert({ directory_entry.path(), spot });
}
}
}
catch(...) {
std::cerr << "Warning - deleting broken lock spot:" << directory_entry.path() << std::endl;
catch(...) {
// this is not totally unexpected... we have a couple of retries to make sure we don't fail on transient states
}
}

if(!read_success && read_retries >= max_read_retries) {
delete_spot = true;
std::cerr << "Warning - deleting broken lock spot:" << directory_entry.path() << std::endl;
}

if(delete_spot) {
Expand Down
8 changes: 7 additions & 1 deletion include/goldilock/process_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,13 @@ namespace tipi::goldilock::process_info {
std::optional<pid_t> match = std::nullopt;

for(const auto& pi : proc_stack) {
for(const auto& needle : process_names) {
for(const auto& needle_raw : process_names) {
#if BOOST_OS_LINUX || BOOST_OS_MACOS
std::string needle = needle_raw.substr(0, 15); // kernel truncates at 15 chars
#else
std::string needle = needle_raw;
#endif

if(tipi::goldilock::string::iequals(pi.name, needle)) {
match = pi.pid;
break;
Expand Down
30 changes: 30 additions & 0 deletions include/goldilock/random.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2024 Yannic Staudt, tipi technologies Ltd and the goldilock contributors
// SPDX-License-Identifier: GPL-2.0-only OR Proprietary
#pragma once

#include <random>
#include <chrono>

namespace tipi::goldilock::random {

template<typename Rep>
inline Rep random_in_range(Rep lower, Rep upper) {
static_assert(std::is_integral<Rep>::value, "Integral required.");

static std::random_device rd;
static std::mt19937 gen(rd());
std::uniform_int_distribution<Rep> dist(lower, upper);
return dist(gen);
}

template<typename Rep, typename Period>
inline std::chrono::duration<Rep, Period> random_sleep_duration(std::chrono::duration<Rep, Period> min, std::chrono::duration<Rep, Period> max) {
std::chrono::duration<Rep, Period> result(random_in_range(min.count(), max.count()));
return result;
}

}




Loading

0 comments on commit 55c00bd

Please sign in to comment.