From dc4b49f7a6e8248dab3e493d9d6924fef520db77 Mon Sep 17 00:00:00 2001 From: Matus Tomlein Date: Wed, 22 Jul 2026 12:34:42 +0200 Subject: [PATCH 1/3] fix(ci): update deprecated GitHub Actions and fix CMake 4 build failures The build and Install workflows were failing on two independent issues: - Deprecated GitHub Actions caused hard failures. `actions/cache@v2` is auto-failed by GitHub; `actions/checkout@v2/v3`, `actions/setup-java@v1`, and `microsoft/setup-msbuild@v1.1` are deprecated. Bump to current major versions (checkout@v4, cache@v4, setup-java@v4 with required distribution, setup-msbuild@v2) and replace the removed `set-output` command with `$GITHUB_OUTPUT`. - CMake 4.x rejects the pinned nlohmann/json (its CMakeLists declared cmake_minimum_required < 3.5) and no longer allows the deprecated `FetchContent_Populate`. Bump nlohmann/json to 3.12.0 (first CMake-4 compatible release) in both the FetchContent pin and the Conan file so the EXACT version match in the installed config stays consistent, and switch the SQLite amalgamation to `FetchContent_MakeAvailable` with `DOWNLOAD_EXTRACT_TIMESTAMP`. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build.yml | 29 ++++++++++++++++------------- .github/workflows/docs.yml | 2 +- .github/workflows/install.yml | 2 +- .github/workflows/release.yml | 4 ++-- CMakeLists.txt | 8 +++----- examples/conanfile.py | 2 +- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8eee471..684c1ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,16 +8,17 @@ jobs: runs-on: macos-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v4 with: - java-version: "11.x" + distribution: "temurin" + java-version: "11" # -- Micro -- - name: Cache Micro id: cache-micro - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: micro.jar key: ${{ runner.os }}-micro @@ -47,16 +48,17 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v4 with: - java-version: "11.x" + distribution: "temurin" + java-version: "11" # -- Micro -- - name: Cache Micro id: cache-micro - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: micro.jar key: ${{ runner.os }}-micro @@ -66,7 +68,7 @@ jobs: run: curl -o micro.jar -L https://github.com/snowplow-incubator/snowplow-micro/releases/download/micro-1.1.2/snowplow-micro-1.1.2.jar - name: Setup MSBuild and add to PATH - uses: microsoft/setup-msbuild@v1.1 + uses: microsoft/setup-msbuild@v2 - name: cmake build run: cmake -D SNOWPLOW_BUILD_TESTS=1 -D SNOWPLOW_BUILD_EXAMPLE=1 -D SNOWPLOW_BUILD_PERFORMANCE=1 . @@ -81,16 +83,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v4 with: - java-version: "11.x" + distribution: "temurin" + java-version: "11" # -- Micro -- - name: Cache Micro id: cache-micro - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: micro.jar key: ${{ runner.os }}-micro diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c2b53ed..7112f10 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 99f3aa6..29f44b6 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -41,7 +41,7 @@ jobs: id: conan uses: turtlebrowser/get-conan@main - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Snowplow dependencies with apt-get run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev uuid-dev if: matrix.system_curl_uuid == 'True' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e7ecb88..0dcf678 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,11 +11,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Get tag version id: version - run: echo ::set-output name=TAG_VERSION::${GITHUB_REF#refs/*/} + run: echo "TAG_VERSION=${GITHUB_REF#refs/*/}" >> "$GITHUB_OUTPUT" - name: Release uses: softprops/action-gh-release@v0.1.7 diff --git a/CMakeLists.txt b/CMakeLists.txt index c67a5ce..35d8b72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,7 @@ set_target_properties(snowplow PROPERTIES # add nlohmann/json library include(FetchContent) -set(NLOHMANN_JSON_VERSION 3.10.5) +set(NLOHMANN_JSON_VERSION 3.12.0) if(SNOWPLOW_USE_EXTERNAL_JSON) find_package(nlohmann_json ${NLOHMANN_JSON_VERSION} REQUIRED) else() @@ -85,10 +85,8 @@ if(SNOWPLOW_USE_EXTERNAL_SQLITE) find_package(SQLite3 REQUIRED) target_link_libraries(snowplow PRIVATE SQLite::SQLite3) else() - FetchContent_Declare(sqlite_amalgamation URL "https://www.sqlite.org/2022/sqlite-amalgamation-3380500.zip") - if (NOT sqlite_amalgamation_POPULATED) - FetchContent_Populate(sqlite_amalgamation) - endif() + FetchContent_Declare(sqlite_amalgamation URL "https://www.sqlite.org/2022/sqlite-amalgamation-3380500.zip" DOWNLOAD_EXTRACT_TIMESTAMP TRUE) + FetchContent_MakeAvailable(sqlite_amalgamation) target_include_directories(snowplow PRIVATE ${sqlite_amalgamation_SOURCE_DIR}) target_sources(snowplow PRIVATE ${sqlite_amalgamation_SOURCE_DIR}/sqlite3.c) endif() diff --git a/examples/conanfile.py b/examples/conanfile.py index ae9892b..646d00d 100644 --- a/examples/conanfile.py +++ b/examples/conanfile.py @@ -7,7 +7,7 @@ class LDDProDepsConan(ConanFile): default_options = {"system_curl_uuid": False} def requirements(self): - self.requires("nlohmann_json/3.11.2") + self.requires("nlohmann_json/3.12.0") self.requires("sqlite3/3.42.0") if not self.options.system_curl_uuid and self.settings.os != "Macos" and (self.settings.os != "Windows" or self.settings.os.subsystem == "cygwin" or self.settings.os.subsystem == "wsl"): self.requires("libcurl/8.0.1") From 08615c41be1539aaf959b0a345a8dd5e38b0b7de Mon Sep 17 00:00:00 2001 From: Matus Tomlein Date: Wed, 22 Jul 2026 12:41:37 +0200 Subject: [PATCH 2/3] fix(ci): use cmake --build on Windows and pipx for Conan Two environment-driven failures surfaced once the workflows ran again on current runners: - Windows MSBuild failed with MSB1009 because it hardcoded `snowplow.sln`, but the newer Visual Studio 18 (2026) generator no longer produces a solution at that path. Replace the MSBuild invocation with the generator-agnostic `cmake --build . --config Debug`, which locates the solution regardless of name. - The Install workflow's `turtlebrowser/get-conan` action runs `pip3 install`, which now fails with an externally-managed-environment (PEP 668) error on GitHub runners. Install Conan via `pipx install conan` instead; pipx is preinstalled on all hosted runners and isolates the venv. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build.yml | 4 ++-- .github/workflows/install.yml | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 684c1ca..0d14f50 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,8 +73,8 @@ jobs: - name: cmake build run: cmake -D SNOWPLOW_BUILD_TESTS=1 -D SNOWPLOW_BUILD_EXAMPLE=1 -D SNOWPLOW_BUILD_PERFORMANCE=1 . - - name: Run MSBuild - run: msbuild .\snowplow.sln + - name: Build with CMake + run: cmake --build . --config Debug - name: Start Micro and run tests run: javaw -jar micro.jar --collector-config test/integration/micro.conf --iglu test/integration/iglu.json; sleep 15; .\Debug\snowplow-tests.exe diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 29f44b6..0911c8c 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -38,8 +38,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Install Conan - id: conan - uses: turtlebrowser/get-conan@main + run: pipx install conan - name: Checkout uses: actions/checkout@v4 - name: Install Snowplow dependencies with apt-get From ba9007d40fa5ba2e02dc0b63452aab831c08647e Mon Sep 17 00:00:00 2001 From: Matus Tomlein Date: Wed, 22 Jul 2026 12:46:46 +0200 Subject: [PATCH 3/3] test(integration): poll Micro for expected counts instead of fixed sleeps Micro processes events asynchronously, so a fixed `sleep_for(1000ms)` before reading counts races it on slower or busier CI runners and causes intermittent failures (AISP-1546; recurrence of CSTMR-1942). A prior sleep-duration increase did not hold. Add `Micro::wait_for_good_and_bad_count(expected_good, expected_bad, timeout)`, which polls the existing `/micro/all` helper every 100ms until the expected counts are reached or a 10s timeout elapses, then returns the last observed counts. Callers keep asserting exact counts, so unexpected extra events are still caught. Replace the fixed waits at each assertion site; the 500ms spacing between the two cookie-test events is intentional (distinct collector timestamps) and is kept and commented. Verified locally against Snowplow Micro 1.1.2: integration section passes and now completes in ~0.3s instead of ~4.5s of fixed sleeps. Co-Authored-By: Claude Opus 4.8 --- test/integration/integration_test.cpp | 16 ++++++---------- test/integration/micro.cpp | 20 ++++++++++++++++++++ test/integration/micro.hpp | 10 ++++++++++ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/test/integration/integration_test.cpp b/test/integration/integration_test.cpp index 03e18a0..b78c623 100644 --- a/test/integration/integration_test.cpp +++ b/test/integration/integration_test.cpp @@ -35,9 +35,8 @@ TEST_CASE("integration") { StructuredEvent sv("hello", "world"); string event_id = tracker->track(sv); tracker->flush(); - sleep_for(milliseconds(1000)); - auto counts = Micro::get_good_and_bad_count(); + auto counts = Micro::wait_for_good_and_bad_count(1, 0); REQUIRE(std::get<0>(counts) == 1); REQUIRE(std::get<1>(counts) == 0); @@ -72,9 +71,8 @@ TEST_CASE("integration") { string event_id = tracker->track(StructuredEvent("hello", "world1")); tracker->track(StructuredEvent("hello", "world2")); tracker->flush(); - sleep_for(milliseconds(1000)); - auto counts = Micro::get_good_and_bad_count(); + auto counts = Micro::wait_for_good_and_bad_count(2, 0); REQUIRE(std::get<0>(counts) == 2); REQUIRE(std::get<1>(counts) == 0); @@ -122,9 +120,8 @@ TEST_CASE("integration") { "{\"currentTime\": 0, \"duration\": 10, \"ended\": false, \"loop\": false, \"muted\": false, \"paused\": false, \"playbackRate\": 1, \"volume\": 100}"_json)}); string event_id = tracker->track(sde); tracker->flush(); - sleep_for(milliseconds(1000)); - auto counts = Micro::get_good_and_bad_count(); + auto counts = Micro::wait_for_good_and_bad_count(1, 0); REQUIRE(std::get<0>(counts) == 1); REQUIRE(std::get<1>(counts) == 0); @@ -152,9 +149,8 @@ TEST_CASE("integration") { string name = "screen"; event.name = &name; tracker->track(event); - sleep_for(milliseconds(1000)); - auto counts = Micro::get_good_and_bad_count(); + auto counts = Micro::wait_for_good_and_bad_count(1, 0); REQUIRE(std::get<0>(counts) == 1); REQUIRE(std::get<1>(counts) == 0); @@ -172,12 +168,12 @@ TEST_CASE("integration") { auto tracker = Snowplow::create_tracker(tracker_config, network_config, emitter_config); tracker->track(StructuredEvent("hello", "1")); + // Space the two events apart so they get distinct collector timestamps. sleep_for(milliseconds(500)); tracker->track(StructuredEvent("hello", "2")); tracker->flush(); - sleep_for(milliseconds(1000)); - auto counts = Micro::get_good_and_bad_count(); + auto counts = Micro::wait_for_good_and_bad_count(2, 0); REQUIRE(std::get<0>(counts) == 2); REQUIRE(std::get<1>(counts) == 0); diff --git a/test/integration/micro.cpp b/test/integration/micro.cpp index 420282b..b3b8a7b 100644 --- a/test/integration/micro.cpp +++ b/test/integration/micro.cpp @@ -13,6 +13,9 @@ See the Apache License Version 2.0 for the specific language governing permissio #include "micro.hpp" +#include +#include + using namespace snowplow; void Micro::clear() { @@ -27,6 +30,23 @@ tuple Micro::get_good_and_bad_count() { return std::make_tuple(good, bad); } +tuple Micro::wait_for_good_and_bad_count( + int expected_good, int expected_bad, int timeout_ms) { + using std::chrono::milliseconds; + using std::chrono::steady_clock; + + const milliseconds poll_interval(100); + auto deadline = steady_clock::now() + milliseconds(timeout_ms); + + tuple counts = get_good_and_bad_count(); + while (steady_clock::now() < deadline && + (std::get<0>(counts) < expected_good || std::get<1>(counts) < expected_bad)) { + std::this_thread::sleep_for(poll_interval); + counts = get_good_and_bad_count(); + } + return counts; +} + list Micro::get_good() { string response_str = request("/micro/good"); return json::parse(response_str).get>(); diff --git a/test/integration/micro.hpp b/test/integration/micro.hpp index abbc906..37a785e 100644 --- a/test/integration/micro.hpp +++ b/test/integration/micro.hpp @@ -34,6 +34,16 @@ class Micro { static tuple get_good_and_bad_count(); static list get_good(); + /** + * Polls Micro until it reports at least the expected number of good and bad + * events, or until the timeout elapses. Micro processes events + * asynchronously, so this avoids racing it with a fixed sleep on slow or + * busy CI runners. Returns the last observed counts either way, so callers + * keep asserting exact counts as before. + */ + static tuple wait_for_good_and_bad_count( + int expected_good, int expected_bad, int timeout_ms = 10000); + private: static string request(const string &path); };