From cc2c13bb8d55c6b7b9fb0881cf11348fc902d783 Mon Sep 17 00:00:00 2001 From: Sincos <72852703+MihaiStreames@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:37:10 +0200 Subject: [PATCH] feat(ensure): `ANYHOW_ENSURE` stringification - add CMakePresets - add sanitizer job on CI - update actions deps --- .editorconfig | 4 +- .github/workflows/ci.yml | 44 +++++++++++++++++----- .github/workflows/release.yml | 2 +- .gitignore | 1 + CHANGELOG.md | 6 +++ CMakeLists.txt | 2 +- CMakePresets.json | 69 +++++++++++++++++++++++++++++++++++ README.md | 2 +- scripts/pin-actions.sh | 0 src/macros.hpp | 20 +++++++--- tests/CMakeLists.txt | 10 ++--- tests/test_macros.cpp | 12 ++++++ 12 files changed, 147 insertions(+), 25 deletions(-) create mode 100644 CMakePresets.json mode change 100644 => 100755 scripts/pin-actions.sh diff --git a/.editorconfig b/.editorconfig index 646e67a..a4d0fd4 100755 --- a/.editorconfig +++ b/.editorconfig @@ -6,7 +6,7 @@ end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true -[*.{cpp,hpp,h}] +[*.{cpp,hpp}] indent_style = space indent_size = 4 max_line_length = 100 @@ -22,7 +22,7 @@ indent_size = 2 [*.sh] indent_style = space indent_size = 2 -end_of_line = lf +max_line_length = 100 [*.md] indent_style = space diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0eccdb..92a6f4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ on: - "**.cpp" - "**/CMakeLists.txt" - "cmake/**" + - ".github/workflows/ci.yml" permissions: {} @@ -16,34 +17,59 @@ concurrency: cancel-in-progress: true jobs: + sanitize: + name: Sanitize + runs-on: ubuntu-latest + permissions: + contents: read # checkout + + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Configure + run: cmake --preset asan + + - name: Build + run: cmake --build --preset asan + + - name: Test + run: ctest --preset asan + check: - name: Check (${{ matrix.os }}) + name: Check (${{ matrix.name }}) runs-on: ${{ matrix.os }} permissions: - contents: read + contents: read # checkout strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] + include: + - { os: ubuntu-latest, cxx: g++, name: linux-gcc } + - { os: ubuntu-latest, cxx: clang++, name: linux-clang } + - { os: macos-latest, name: apple-clang } + - { os: windows-latest, name: windows-msvc } steps: - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Configure env: - CXX_STD: "20" + CXX_COMPILER: ${{ matrix.cxx }} shell: bash - run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DANYHOW_BUILD_TESTS=ON "-DCMAKE_CXX_STANDARD=$CXX_STD" + run: cmake --preset ci "${CXX_COMPILER:+-DCMAKE_CXX_COMPILER=$CXX_COMPILER}" - name: Lint - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubuntu-latest' && matrix.cxx == 'clang++' run: find tests -name '*.cpp' | xargs clang-tidy -p build - name: Build - run: cmake --build build --config Release + run: cmake --build --preset ci - name: Test - run: ctest --test-dir build --build-config Release --output-on-failure + run: ctest --preset ci diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d4a233..33b9914 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: contents: write # create GitHub release and upload assets steps: - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false diff --git a/.gitignore b/.gitignore index 9cd0daf..388cc20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ ### Build directories ### build/ +build-asan/ out/ cmake-build-*/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f0e740..d410500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project. +## [0.2.0] - 2026-07-08 + +### Added + +- `ANYHOW_ENSURE` stringification (embedding failing condition as a string in the error message) + ## [0.1.0] - 2026-06-16 ### Added diff --git a/CMakeLists.txt b/CMakeLists.txt index c5a66bd..9dda0dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.22) project( anyhow-cpp - VERSION 0.1.0 + VERSION 0.2.0 DESCRIPTION "Rust's anyhow, ported to C++20." LANGUAGES CXX) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..23eb681 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,69 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "dev", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CXX_STANDARD": "20", + "ANYHOW_BUILD_TESTS": "ON" + } + }, + { + "name": "asan", + "inherits": "dev", + "binaryDir": "${sourceDir}/build-asan", + "cacheVariables": { + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_CXX_FLAGS": "-fsanitize=address,undefined -fno-omit-frame-pointer" + } + }, + { + "name": "ci", + "inherits": "dev", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ], + "buildPresets": [ + { + "name": "dev", + "configurePreset": "dev" + }, + { + "name": "asan", + "configurePreset": "asan" + }, + { + "name": "ci", + "configurePreset": "ci", + "configuration": "Release" + } + ], + "testPresets": [ + { + "name": "dev", + "configurePreset": "dev", + "output": { + "outputOnFailure": true + } + }, + { + "name": "asan", + "configurePreset": "asan", + "output": { + "outputOnFailure": true + } + }, + { + "name": "ci", + "configurePreset": "ci", + "configuration": "Release", + "output": { + "outputOnFailure": true + } + } + ] +} diff --git a/README.md b/README.md index d28104a..44ef5fb 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ include(FetchContent) FetchContent_Declare( anyhow-cpp GIT_REPOSITORY https://github.com/MihaiStreames/anyhow-cpp.git - GIT_TAG v0.1.0 + GIT_TAG v0.2.0 ) FetchContent_MakeAvailable(anyhow-cpp) diff --git a/scripts/pin-actions.sh b/scripts/pin-actions.sh old mode 100644 new mode 100755 diff --git a/src/macros.hpp b/src/macros.hpp index 409a92c..aba5fcd 100644 --- a/src/macros.hpp +++ b/src/macros.hpp @@ -38,13 +38,23 @@ /// Early-return a failure. #define ANYHOW_BAIL(msg, ...) return ::anyhow::fail(msg __VA_OPT__(, ) __VA_ARGS__) -/// Early-return a failure if `cond` is false. -#define ANYHOW_ENSURE(cond, msg, ...) \ +#define ANYHOW_ENSURE_IMPL_MSG(cond, ...) \ do { \ - if (!(cond)) \ - ANYHOW_BAIL(msg __VA_OPT__(, ) __VA_ARGS__); \ + if (!(cond)) { \ + ANYHOW_BAIL(__VA_ARGS__); \ + } \ } while (0) +#define ANYHOW_ENSURE_IMPL_(cond) ANYHOW_ENSURE_IMPL_MSG(cond, "Condition failed: " #cond) + +// i couldn't come up with a better solution than this +// VA_OPT(MSG) token-pastes into ANYHOW_ENSURE_IMPL_MSG when message given +// otherwise ANYHOW_ENSURE_IMPL_ + +/// Early-return a failure if `cond` is false. +#define ANYHOW_ENSURE(cond, ...) \ + ANYHOW_ENSURE_IMPL_##__VA_OPT__(MSG)(cond __VA_OPT__(, ) __VA_ARGS__) + // opt-in short aliases // define ANYHOW_SHORT_MACROS before including to enable @@ -54,6 +64,6 @@ #define TRY_CATCH(expr, cleanup) ANYHOW_TRY_CATCH(expr, cleanup) #define TRY_ASSIGN(var_out, expr) ANYHOW_TRY_ASSIGN(var_out, expr) #define BAIL(msg, ...) ANYHOW_BAIL(msg __VA_OPT__(, ) __VA_ARGS__) - #define ENSURE(cond, msg, ...) ANYHOW_ENSURE(cond, msg __VA_OPT__(, ) __VA_ARGS__) + #define ENSURE(cond, ...) ANYHOW_ENSURE(cond __VA_OPT__(, ) __VA_ARGS__) #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fc00a92..66009fa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,19 +13,18 @@ set(BUILD_GMOCK CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) -# Unit tests ### - +# Unit tests set(ANYHOW_TEST_SOURCES test_expected.cpp test_context.cpp test_fmt.cpp test_scope_guard.cpp test_macros.cpp test_failure.cpp) add_executable(anyhow-tests ${ANYHOW_TEST_SOURCES}) target_link_libraries(anyhow-tests PRIVATE anyhow::anyhow GTest::gtest_main) -target_compile_features(anyhow-tests PRIVATE cxx_std_20) +set_target_properties(anyhow-tests PROPERTIES CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON) target_compile_definitions(anyhow-tests PRIVATE ANYHOW_SHORT_MACROS) # __VA_OPT__ is C++20 but MSVC requires an explicit conformance flag to enable -# it if(MSVC) target_compile_options(anyhow-tests PRIVATE /Zc:preprocessor) endif() @@ -34,8 +33,7 @@ target_include_directories(anyhow-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) include(GoogleTest) gtest_discover_tests(anyhow-tests) -# Fuzz target (clang libFuzzer only) ### - +# Fuzz target (clang libFuzzer only) option(ANYHOW_BUILD_FUZZ "Build libFuzzer fuzz targets (requires clang + -fsanitize=fuzzer)" OFF) diff --git a/tests/test_macros.cpp b/tests/test_macros.cpp index a1de26c..d5bcc42 100644 --- a/tests/test_macros.cpp +++ b/tests/test_macros.cpp @@ -53,6 +53,11 @@ static anyhow::Expected ensure_fails(int val) { return {val}; } +static anyhow::Expected ensure_no_msg(int val) { + ANYHOW_ENSURE(val > 0); + return {val}; +} + TEST(Macros, TryPropagatesFailure) { auto res = try_propagate(); @@ -114,3 +119,10 @@ TEST(Macros, EnsureFailsWhenCondFalse) { EXPECT_EQ(res.failure().error.message, "must be positive"); EXPECT_EQ(res.failure().error.domain, "validation"); } + +TEST(Macros, EnsureAutoMessage) { + auto res = ensure_no_msg(-1); + ASSERT_TRUE(res.failed()); + EXPECT_EQ(res.failure().error.message, "Condition failed: val > 0"); + EXPECT_EQ(res.failure().error.domain, ""); +}