Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
14ea301
ci: sentry implementaion
jcardonne Nov 5, 2025
6f62d34
test(editor): add unit tests for editor components
jcardonne Dec 2, 2025
ce54191
test(editor): add ImGui context testing and expand test coverage
jcardonne Dec 3, 2025
42b7628
test(editor): add WindowRegistry unit tests
jcardonne Dec 3, 2025
68dcebe
test: add unit tests for common, engine, and renderer modules
jcardonne Dec 10, 2025
d28c246
test(engine): add ECS Access and SingletonComponent unit tests
jcardonne Dec 10, 2025
bea347b
test(engine): add unit tests for types, ECS, and assets
jcardonne Dec 10, 2025
0de104e
test(engine): add unit tests for components, scripting, and core modules
jcardonne Dec 12, 2025
3e7888c
test(engine,renderer): add comprehensive unit tests for coverage impr…
jcardonne Dec 12, 2025
bd67f08
test(engine,renderer,editor): add unit tests for pixel conversion, wi…
jcardonne Dec 12, 2025
bcdb044
test(engine): add ShapeType enum tests and fix ParentComponent test
jcardonne Dec 12, 2025
75b90c5
test: add comprehensive ECS and component unit tests
jcardonne Dec 12, 2025
3227fb7
test(engine): add comprehensive unit tests for ECS and core components
jcardonne Dec 12, 2025
9e7c645
test: add comprehensive renderer and ECS edge case tests
jcardonne Dec 12, 2025
37cc816
test: add comprehensive edge case tests for core utilities and compon…
jcardonne Dec 12, 2025
e7ba136
test: add unit tests for systems, factories, and serialization
jcardonne Dec 12, 2025
b5d0b67
test: add comprehensive tests for editor and engine components
jcardonne Dec 13, 2025
99f234d
test: add comprehensive unit tests for events, exceptions, scripting,…
jcardonne Dec 13, 2025
5c0c4a7
test: add unit tests for renderer, ECS, and event systems
jcardonne Dec 13, 2025
311aa74
fix(ci): resolve CI/CD failures on ci/sentry branch
jcardonne Dec 13, 2025
a9018b2
fix(ci): use sonarqube-scan-action instead of CLI
jcardonne Dec 13, 2025
90ffc43
fix(test): add missing algorithm header for std::sort/unique
jcardonne Dec 13, 2025
156b01a
fix(test): use cross-platform temp paths for Windows compatibility
jcardonne Dec 13, 2025
5e8c9c4
fix(test): skip OpenGL tests instead of failing when context unavailable
jcardonne Dec 13, 2025
9fc135d
fix(test): fix TearDown crashes when OpenGL context is unavailable
jcardonne Dec 13, 2025
d016155
fix(test): prevent double-free in OpenGL test fixtures on headless CI
jcardonne Dec 13, 2025
5ec2268
fix(test): fix Windows CI test failures
jcardonne Dec 13, 2025
f9937c8
fix(ecs): add bounds check to getComponentArray to prevent crash on W…
jcardonne Dec 13, 2025
36ac591
ci: make sonar-scanner non-blocking due to internal bugs
jcardonne Dec 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ jobs:
run: |
git submodule update --init --recursive

- name: Install sonar-scanner
- name: Install sonar-scanner and build-wrapper
if: ${{ matrix.os == 'ubuntu-22.04' }}
uses: sonarsource/sonarcloud-github-c-cpp@v3
uses: SonarSource/sonarqube-scan-action/install-build-wrapper@v4

- name: Install latest CMake and Ninja
uses: lukka/get-cmake@latest
Expand Down Expand Up @@ -209,13 +209,15 @@ jobs:

- name: Run sonar-scanner
if: ${{ matrix.os == 'ubuntu-22.04' }}
continue-on-error: true # SonarScanner has intermittent internal bugs
uses: SonarSource/sonarqube-scan-action@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
run: |
sonar-scanner \
--define sonar.cfamily.compile-commands="./build/compile_commands.json" \
--define sonar.coverageReportPaths=coverage.xml
with:
args: >
-Dsonar.cfamily.compile-commands=./build/compile_commands.json
-Dsonar.coverageReportPaths=coverage.xml

- name: Install nexoEditor
shell: bash
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
env:
USERNAME: NexoEngine
with:
Expand All @@ -140,6 +140,6 @@ jobs:
CMAKE_CXX_COMPILER: ${{ matrix.compiler == 'clang' && steps.set-up-clang.outputs.clangxx || matrix.compiler == 'gcc' && steps.set-up-gcc.outputs.gxx || '' }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,4 @@ html/
_CPack_Packages/

myinstall/
*.gcov
54 changes: 53 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ set(NEXO_BOOTSTRAP_VCPKG OFF CACHE BOOL "Enable vcpkg bootstrap")
set(NEXO_BUILD_TESTS ON CACHE BOOL "Enable tests")
set(NEXO_BUILD_EXAMPLES OFF CACHE BOOL "Enable examples")
set(NEXO_GRAPHICS_API "OpenGL" CACHE STRING "Graphics API to use")
set(NEXO_ENABLE_SENTRY ON CACHE BOOL "Enable Sentry crash tracking")
set(NEXO_SENTRY_DEBUG_MODE OFF CACHE BOOL "Enable Sentry debug mode (local file output)")

if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(NEXO_ENABLE_SENTRY OFF CACHE BOOL "Enable Sentry crash tracking" FORCE)
if (NOT DEFINED NEXO_SENTRY_DEBUG_MODE OR NEXO_SENTRY_DEBUG_MODE)
set(NEXO_SENTRY_DEBUG_MODE ON CACHE BOOL "Enable Sentry debug mode (local file output)" FORCE)
endif()
endif()

if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
set(NEXO_COMPILER_FLAGS_ALL --std=c++${CMAKE_CXX_STANDARD})
Expand All @@ -23,15 +32,25 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
set(NEXO_LINKER_FLAGS_ALL "")
set(NEXO_LINKER_FLAGS_DEBUG "")
set(NEXO_LINKER_FLAGS_RELEASE "-flto")

if (NEXO_ENABLE_SENTRY OR NEXO_SENTRY_DEBUG_MODE)
list(APPEND NEXO_COMPILER_FLAGS_RELEASE -g -funwind-tables -fno-omit-frame-pointer)
list(APPEND NEXO_COMPILER_FLAGS_DEBUG -funwind-tables -fno-omit-frame-pointer)
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
set(NEXO_COMPILER_FLAGS_ALL /nologo /W4 /std:c++${CMAKE_CXX_STANDARD} /Zc:preprocessor /utf-8)
set(NEXO_COMPILER_FLAGS_DEBUG /Zi /Od /Zc:preprocessor /MDd /D_DEBUG /D_ITERATOR_DEBUG_LEVEL=2 /D_SECURE_SCL=1)
set(NEXO_COMPILER_FLAGS_RELEASE /O2 /Zc:preprocessor /DNDEBUG /MD)
set(NEXO_COVERAGE_FLAGS "") # MSVC doesn't support coverage in the same way
set(NEXO_COVERAGE_FLAGS "")

set(NEXO_LINKER_FLAGS_ALL "")
set(NEXO_LINKER_FLAGS_DEBUG "")
set(NEXO_LINKER_FLAGS_RELEASE "/LTCG")

if (NEXO_ENABLE_SENTRY OR NEXO_SENTRY_DEBUG_MODE)
list(APPEND NEXO_COMPILER_FLAGS_RELEASE /Zi)
list(APPEND NEXO_LINKER_FLAGS_RELEASE /DEBUG /OPT:REF /OPT:ICF)
endif()
else()
message(WARNING "Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}, using default flags")
endif()
Expand Down Expand Up @@ -106,6 +125,39 @@ message(STATUS "Running VCPKG...")
include("${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake")
message(STATUS "VCPKG done.")

# SENTRY CONFIGURATION
if (NEXO_ENABLE_SENTRY OR NEXO_SENTRY_DEBUG_MODE)
message(STATUS "Sentry crash tracking enabled")
find_package(sentry CONFIG REQUIRED)

if (NEXO_ENABLE_SENTRY)
add_compile_definitions(NEXO_SENTRY_ENABLED)
message(STATUS " Mode: Production (network reporting)")
endif()

if (NEXO_SENTRY_DEBUG_MODE)
add_compile_definitions(NEXO_SENTRY_DEBUG_MODE)
message(STATUS " Mode: Debug (local file output)")
endif()

find_package(Git QUIET)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags --always --dirty
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE NEXO_GIT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(NEXO_GIT_VERSION)
add_compile_definitions(NEXO_SENTRY_RELEASE="${NEXO_GIT_VERSION}")
message(STATUS " Release version: ${NEXO_GIT_VERSION}")
endif()
endif()
else()
message(STATUS "Sentry crash tracking disabled")
endif()

# SETUP EDITOR
include("${CMAKE_CURRENT_SOURCE_DIR}/editor/CMakeLists.txt")
# SETUP ENGINE
Expand Down
4 changes: 4 additions & 0 deletions common/Exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@

#include <format>

#if defined(NEXO_SENTRY_ENABLED) || defined(NEXO_SENTRY_DEBUG_MODE)
namespace nexo::crash { class CrashTracker; }
#endif

namespace nexo {
const char *Exception::what() const noexcept
{
Expand Down
217 changes: 217 additions & 0 deletions common/String.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
#pragma once

#include <string_view>
#include <string>
#include <vector>
#include <ranges>
#include <algorithm>

namespace nexo {
/**
Expand All @@ -37,5 +40,219 @@ namespace nexo {
});
}

/**
* @brief Convert a string to lowercase.
*
* @param str The string to convert.
* @return A new string with all characters in lowercase.
*/
[[nodiscard]] inline std::string toLower(std::string_view str) {
std::string result;
result.reserve(str.size());
std::ranges::transform(str, std::back_inserter(result), [](const char c) {
return static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
});
return result;
}

/**
* @brief Convert a string to uppercase.
*
* @param str The string to convert.
* @return A new string with all characters in uppercase.
*/
[[nodiscard]] inline std::string toUpper(std::string_view str) {
std::string result;
result.reserve(str.size());
std::ranges::transform(str, std::back_inserter(result), [](const char c) {
return static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
});
return result;
}

/**
* @brief Check if a string starts with a given prefix.
*
* @param str The string to check.
* @param prefix The prefix to search for.
* @return true if str starts with prefix, false otherwise.
*/
[[nodiscard]] constexpr bool startsWith(const std::string_view& str, const std::string_view& prefix) {
return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix;
}

/**
* @brief Check if a string starts with a given prefix (case-insensitive).
*
* @param str The string to check.
* @param prefix The prefix to search for.
* @return true if str starts with prefix (case-insensitive), false otherwise.
*/
[[nodiscard]] constexpr bool istartsWith(const std::string_view& str, const std::string_view& prefix) {
if (str.size() < prefix.size()) return false;
return iequals(str.substr(0, prefix.size()), prefix);
}

/**
* @brief Check if a string ends with a given suffix.
*
* @param str The string to check.
* @param suffix The suffix to search for.
* @return true if str ends with suffix, false otherwise.
*/
[[nodiscard]] constexpr bool endsWith(const std::string_view& str, const std::string_view& suffix) {
return str.size() >= suffix.size() && str.substr(str.size() - suffix.size()) == suffix;
}

/**
* @brief Check if a string ends with a given suffix (case-insensitive).
*
* @param str The string to check.
* @param suffix The suffix to search for.
* @return true if str ends with suffix (case-insensitive), false otherwise.
*/
[[nodiscard]] constexpr bool iendsWith(const std::string_view& str, const std::string_view& suffix) {
if (str.size() < suffix.size()) return false;
return iequals(str.substr(str.size() - suffix.size()), suffix);
}

/**
* @brief Trim whitespace from the left side of a string.
*
* @param str The string to trim.
* @return A new string with leading whitespace removed.
*/
[[nodiscard]] inline std::string ltrim(std::string_view str) {
const auto start = std::ranges::find_if(str, [](const char c) {
return !std::isspace(static_cast<unsigned char>(c));
});
return std::string(start, str.end());
}

/**
* @brief Trim whitespace from the right side of a string.
*
* @param str The string to trim.
* @return A new string with trailing whitespace removed.
*/
[[nodiscard]] inline std::string rtrim(std::string_view str) {
const auto end = std::ranges::find_if(str | std::views::reverse, [](const char c) {
return !std::isspace(static_cast<unsigned char>(c));
}).base();
return std::string(str.begin(), end);
}

/**
* @brief Trim whitespace from both sides of a string.
*
* @param str The string to trim.
* @return A new string with leading and trailing whitespace removed.
*/
[[nodiscard]] inline std::string trim(std::string_view str) {
return ltrim(rtrim(str));
}

/**
* @brief Check if a string contains a substring.
*
* @param str The string to search in.
* @param substr The substring to search for.
* @return true if str contains substr, false otherwise.
*/
[[nodiscard]] constexpr bool contains(const std::string_view& str, const std::string_view& substr) {
return str.find(substr) != std::string_view::npos;
}

/**
* @brief Check if a string contains a substring (case-insensitive).
*
* @param str The string to search in.
* @param substr The substring to search for.
* @return true if str contains substr (case-insensitive), false otherwise.
*/
[[nodiscard]] inline bool icontains(const std::string_view& str, const std::string_view& substr) {
if (substr.empty()) return true;
if (str.size() < substr.size()) return false;

for (size_t i = 0; i <= str.size() - substr.size(); ++i) {
if (iequals(str.substr(i, substr.size()), substr)) {
return true;
}
}
return false;
}

/**
* @brief Replace all occurrences of a substring with another string.
*
* @param str The original string.
* @param from The substring to replace.
* @param to The replacement string.
* @return A new string with all occurrences replaced.
*/
[[nodiscard]] inline std::string replaceAll(std::string_view str, std::string_view from, std::string_view to) {
if (from.empty()) return std::string(str);

std::string result;
result.reserve(str.size());
size_t start_pos = 0;
size_t pos;

while ((pos = str.find(from, start_pos)) != std::string_view::npos) {
result.append(str.substr(start_pos, pos - start_pos));
result.append(to);
start_pos = pos + from.size();
}
result.append(str.substr(start_pos));
return result;
}

/**
* @brief Split a string by a delimiter.
*
* @param str The string to split.
* @param delimiter The delimiter to split by.
* @return A vector of substrings.
*/
[[nodiscard]] inline std::vector<std::string> split(std::string_view str, std::string_view delimiter) {
std::vector<std::string> result;
if (delimiter.empty()) {
result.emplace_back(str);
return result;
}

size_t start = 0;
size_t end;

while ((end = str.find(delimiter, start)) != std::string_view::npos) {
result.emplace_back(str.substr(start, end - start));
start = end + delimiter.size();
}
result.emplace_back(str.substr(start));
return result;
}

/**
* @brief Join a collection of strings with a delimiter.
*
* @param strings The strings to join.
* @param delimiter The delimiter to use.
* @return A single string with all elements joined.
*/
template<typename Container>
[[nodiscard]] inline std::string join(const Container& strings, std::string_view delimiter) {
if (strings.empty()) return "";

std::string result;
auto it = strings.begin();
result.append(*it);
++it;

for (; it != strings.end(); ++it) {
result.append(delimiter);
result.append(*it);
}
return result;
}

} // namespace nexo
Loading
Loading