Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
PolarGoose committed Jan 7, 2023
0 parents commit d968f16
Show file tree
Hide file tree
Showing 49 changed files with 1,571 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
# https://zed0.co.uk/clang-format-configurator/
BasedOnStyle: LLVM
CompactNamespaces: true
FixNamespaceComments: false
PointerAlignment: Left
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
21 changes: 21 additions & 0 deletions .github/workflows/continuous-integration-workflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
on: push

jobs:
build:
runs-on: windows-2022
steps:
- uses: actions/checkout@v3
- uses: ilammy/msvc-dev-cmd@v1
- run: ./build.ps1
- uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
files: out/publish/*.zip
fail_on_unmatched_files: true
- uses: actions/upload-artifact@v3
with:
name: Build artifacts
path: out/publish/*.zip
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/out/
.vs/
.idea/
CMakeSettings.json
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cmake_minimum_required (VERSION 3.17)
project(UtilsHLibrary LANGUAGES CXX)

include(tools/cmake/compiler_options.cmake)
include(tools/cmake/clang_format.cmake)
include(tools/cmake/catch2_lib.cmake)

file(GLOB_RECURSE src_files CONFIGURE_DEPENDS "src/*.h" "src/*.cpp")
add_executable(utils_h_test ${src_files})
target_include_directories(utils_h_test PRIVATE "src")
target_precompile_headers(utils_h_test PRIVATE src/test/precompiled_header.h)
add_clang_format(utils_h_test)
add_catch2_lib(utils_h_test)
add_compiler_options_with_warnings(utils_h_test)
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2018 PolarGoose

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
This project contains a small header only C++ library of helpful utilities.

## How to use this library
As it is a header only library, you only need to add the headers from the "src/utils_h" folder to your project.
48 changes: 48 additions & 0 deletions build.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Function Info($msg) {
Write-Host -ForegroundColor DarkGreen "`nINFO: $msg`n"
}

Function Error($msg) {
Write-Host `n`n
Write-Error $msg
exit 1
}

Function CheckReturnCodeOfPreviousCommand($msg) {
if(-Not $?) {
Error "${msg}. Error code: $LastExitCode"
}
}

Function CreateZipArchive($directory, $archiveFile) {
Info "Create a zip archive from `n '$directory' `n to `n '$archiveFile'"
New-Item $archiveFile -Force -ItemType File > $null
Compress-Archive -Force -Path $directory -DestinationPath $archiveFile
}

Function ForceCopy($srcFile, $dstFile) {
Info "Copy `n '$srcFile' `n to `n '$dstFile'"
New-Item $dstFile -Force -ItemType File > $null
Copy-Item $srcFile -Destination $dstFile -Force
}

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$root = Resolve-Path $PSScriptRoot
$buildDir = "$root/out"
$publishDir = "$buildDir/publish"

Info "Build the project using Cmake"
Info "Cmake generation phase"
cmake -S $root -B $buildDir -G Ninja -DCMAKE_BUILD_TYPE=Release
CheckReturnCodeOfPreviousCommand "Cmake generation phase failed"
Info "Cmake build phase"
cmake --build $buildDir
CheckReturnCodeOfPreviousCommand "Cmake building phase failed"

Info "Run tests"
& $buildDir/utils_h_test.exe
CheckReturnCodeOfPreviousCommand "Tests failed"

CreateZipArchive $root/src/utils_h $publishDir/utils_h.zip
28 changes: 28 additions & 0 deletions src/test/bit_cast_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "utils_h/bit_cast.h"
#include "utils_h/struct_aliasing_disable.h"

using namespace utils_h;

template <typename Source, typename Dest> void bit_cast_test(const Source src) {
const auto dst = bit_cast<Dest>(src);
REQUIRE(memcmp(&dst, &src, sizeof(src)) == 0);

const auto new_src = bit_cast<Source>(dst);
REQUIRE(memcmp(&new_src, &src, sizeof(src)) == 0);
}

UTILS_H_DISABLE_STRUCT_ALIASING_BEGIN
struct struct_of_64_bytes {
uint8_t a;
uint32_t b;
uint8_t c;
uint8_t d;
uint8_t e;
};
UTILS_H_DISABLE_STRUCT_ALIASING_END

TEST_CASE("bit_cast - Should convert data back and forth") {
bit_cast_test<float, uint32_t>(1.2F);
bit_cast_test<char, uint8_t>('a');
bit_cast_test<uint64_t, struct_of_64_bytes>(0x12af43db4589a7c4);
}
53 changes: 53 additions & 0 deletions src/test/circular_buffer_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include "utils_h/circular_buffer.h"

using namespace utils_h;

class circular_buffer_test {
protected:
void fill_buffer() {
for (size_t i{0}; i < m_capacity; i++) {
m_buffer.put(i);
}
}

const size_t m_capacity{100};
circular_buffer<size_t> m_buffer{m_capacity};
};

TEST_CASE_METHOD(circular_buffer_test, "Capacity should return correct value") {
REQUIRE(m_buffer.capacity() == m_capacity);
}

TEST_CASE_METHOD(
circular_buffer_test,
"Get method should throw an exception when the buffer is empty") {
REQUIRE(m_buffer.is_empty() == true);
REQUIRE_THROWS_AS(m_buffer.get(), circular_buffer_is_empty_exception);
}

TEST_CASE_METHOD(
circular_buffer_test,
"Buffer can contain the amount of elements equal to its capacity") {
fill_buffer();

REQUIRE(m_buffer.size() == m_capacity);
REQUIRE(m_buffer.is_full() == true);

for (size_t i{0}; i < m_capacity; i++) {
REQUIRE(m_buffer.get() == i);
}

REQUIRE(m_buffer.size() == 0);
REQUIRE(m_buffer.is_empty() == true);
}

TEST_CASE_METHOD(circular_buffer_test, "Adding one element to a full buffer "
"should remove its last added element") {
fill_buffer();

m_buffer.put(m_capacity);

for (size_t i{0}; i < m_capacity; i++) {
REQUIRE(m_buffer.get() == i + 1);
}
}
73 changes: 73 additions & 0 deletions src/test/concurrency/dispatcher_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "utils_h/concurrency/dispatcher.h"

using namespace utils_h;
using namespace utils_h::concurrency;

class dispatcher_test {
protected:
void wait_while_all_tasks_are_executed() {
_dispatcher.dispatch_and_wait([] {});
}

void unhandled_exception_handler() {
_unhandled_exception_handler_was_called = true;
}

dispatcher _dispatcher{
[&](auto& /* exception */) { unhandled_exception_handler(); }};
bool _unhandled_exception_handler_was_called{false};
};

TEST_CASE_METHOD(
dispatcher_test,
"dispatch_and_wait function should work with functions which return void") {
_dispatcher.dispatch_and_wait([&] {});
}

TEST_CASE_METHOD(dispatcher_test, "dispatch_and_wait function should work with "
"functions which return value") {
REQUIRE(_dispatcher.dispatch_and_wait([] { return std::string{"string"}; }) ==
"string");
REQUIRE(_dispatcher.dispatch_and_wait([] { return 1; }) == 1);
}

TEST_CASE_METHOD(dispatcher_test,
"If a dispatched task throws an exception, an unhandled "
"exception handler should be called") {
_dispatcher.dispatch([] { throw std::runtime_error("error"); });
wait_while_all_tasks_are_executed();

REQUIRE(_unhandled_exception_handler_was_called == true);
}

TEST_CASE_METHOD(dispatcher_test, "Exception during dispatch_and_wait function "
"call should be propagated to the caller") {
REQUIRE_THROWS_AS(
_dispatcher.dispatch_and_wait([] { throw std::runtime_error("error"); }),
std::runtime_error);
}

TEST_CASE_METHOD(dispatcher_test,
"is_dispatcher_thread function returns true if it is called "
"inside a dispatcher thread") {
REQUIRE(_dispatcher.is_dispatcher_thread() == false);
REQUIRE(_dispatcher.dispatch_and_wait(
[&] { return _dispatcher.is_dispatcher_thread(); }) == true);
}

TEST_CASE_METHOD(
dispatcher_test,
"Tasks should be executed in the order in which they were dispatched") {
std::vector<size_t> results;

for (size_t i = 0; i < 100; i++) {
_dispatcher.dispatch([i, &results] { results.push_back(i); });
}

wait_while_all_tasks_are_executed();
REQUIRE(results.size() == 100);

for (size_t i = 0; i < 100; i++) {
REQUIRE(results[i] == i);
}
}
92 changes: 92 additions & 0 deletions src/test/concurrency/interruptible_condition_variable_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include "utils/time_passed_method.h"
#include "utils_h/concurrency/interruptible_condition_variable.h"

using namespace utils_h;
using namespace utils_h::concurrency;
using namespace std::chrono_literals;

class interruptible_condition_variable_test : protected has_time_passed_method {
protected:
std::mutex _mutex;
interruptible_condition_variable _cond_var;
};

TEST_CASE_METHOD(interruptible_condition_variable_test,
"wait() method should throw an exception on time out") {
std::unique_lock lock{_mutex};

REQUIRE_THROWS_AS(_cond_var.wait_for(
lock, [] { return false; }, 10ms),
timed_out_exception);
}

TEST_CASE_METHOD(interruptible_condition_variable_test,
"wait() method should throw an exception when interrupted") {
auto client_func{[&] {
try {
std::unique_lock lock{_mutex};
_cond_var.wait_for(
lock, [] { return false; }, 1s);
} catch (const interrupted_exception&) {
return true;
}
return false;
}};

auto client1_is_interrupted{false};
auto client2_is_interrupted{false};

std::thread client1{[&] { client1_is_interrupted = client_func(); }};
std::thread client2{[&] { client2_is_interrupted = client_func(); }};

// sleep long enough to let client1 and client2 threads to get blocked on
// _cond_var.wait_for() call
std::this_thread::sleep_for(300ms);

// interrupt client1 and client2
{
std::unique_lock lock{_mutex};
_cond_var.interrupt(lock);
}

// wait until client1 and client2 threads get notified about the interruption
// and finished
client1.join();
client2.join();

REQUIRE(client1_is_interrupted == true);
REQUIRE(client2_is_interrupted == true);
}

TEST_CASE_METHOD(
interruptible_condition_variable_test,
"After being interrupted, all calls to wait() should throw an exception") {
std::unique_lock lock{_mutex};
_cond_var.interrupt(lock);

REQUIRE_THROWS_AS(_cond_var.wait_for(lock, [] { return false; }),
interrupted_exception);
}

TEST_CASE_METHOD(
interruptible_condition_variable_test,
"wait() method should unblock when the condition becomes true") {
auto value_to_wait_for{false};

std::thread client{[&] {
std::unique_lock lock{_mutex};
_cond_var.wait_for(lock, [&] { return value_to_wait_for; });
}};

// wait long enough to let client thread to get blocked on wait_for() method
std::this_thread::sleep_for(300ms);

// set the wait condition to true and wake up the client
{
std::unique_lock lock{_mutex};
value_to_wait_for = true;
_cond_var.notify_all();
}

client.join();
}
Loading

0 comments on commit d968f16

Please sign in to comment.