Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5100366
Fix cached configs_hash being cleared on every launch, not only on id…
Gogoshika-ga Aug 31, 2026
c5c85ac
Guard remote configs listeners and json rebuild with state mutex
Gogoshika-ga Aug 31, 2026
74893f0
Add regression tests for configs_hash persistence across launches
Gogoshika-ga Aug 31, 2026
eb24d1d
Fix custom fields payload discarded when an illegal key holds a non-s…
Gogoshika-ga Aug 31, 2026
0c1a806
Add custom fields validation tests
Gogoshika-ga Aug 31, 2026
8a0046b
Use atomic gcov counter updates in coverage builds
Gogoshika-ga Aug 31, 2026
d4b1ae2
Disable event submission in unit tests
Gogoshika-ga Aug 31, 2026
9de361a
Add event store and send queue tests using mock http client
Gogoshika-ga Aug 31, 2026
44de470
Bump version to 5.4.1 and update changelog
Gogoshika-ga Aug 31, 2026
b4da568
Add public API integration tests
Gogoshika-ga Aug 31, 2026
cf7575a
Fix local coverage report generation with lcov 2.x
Gogoshika-ga Aug 31, 2026
e36ba97
Update coverage report action to v7.2.0
Gogoshika-ga Aug 31, 2026
510a073
Cache vcpkg binary archives in CI
Gogoshika-ga Aug 31, 2026
ead2c1f
Preserve branch data in CI coverage and fix changed-files matching
Gogoshika-ga Aug 31, 2026
7db3e4b
Fix session end test isolation from stale ga_session rows
Gogoshika-ga Aug 31, 2026
0d067a8
Pin lcov 2.3.2 in CI and check GA thread drain in teardown
Gogoshika-ga Aug 31, 2026
6fd4d3b
Update CMakeLists.txt
Gogoshika-ga Aug 31, 2026
800dfd8
Update coverage.yml
Gogoshika-ga Aug 31, 2026
c24d712
Update coverage.yml
Gogoshika-ga Aug 31, 2026
a639d47
Update CMakeLists.txt
Gogoshika-ga Aug 31, 2026
954f28d
fix
Gogoshika-ga Aug 31, 2026
e9d7c1f
remove code coverage target and CI job
Gogoshika-ga Sep 1, 2026
3922a49
add gcovr-based code coverage (local via setup.py, gated CI job)
Gogoshika-ga Sep 1, 2026
438ce03
coverage: gcovr.cfg, Coveralls/Codecov upload, Pages publish, badges,…
Gogoshika-ga Sep 1, 2026
3e148e7
drop Coveralls, keep Codecov only
Gogoshika-ga Sep 1, 2026
fb4f40e
recalibrate coverage gates to real gcc CI baseline
Gogoshika-ga Sep 1, 2026
9867a8c
only run report steps when the build itself succeeded
Gogoshika-ga Sep 1, 2026
866c440
update cache CI action
Gogoshika-ga Sep 1, 2026
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
8 changes: 8 additions & 0 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ jobs:
echo "check lld..."
ldd --version

- name: Cache vcpkg binaries
if: matrix.dependency_mode != 'no_deps'
uses: actions/cache@v5
with:
path: ${{ runner.os == 'Windows' && '~/AppData/Local/vcpkg/archives' || '~/.cache/vcpkg/archives' }}
key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }}
restore-keys: vcpkg-${{ runner.os }}-

- name: Install vcpkg (Linux/macOS)
if: runner.os != 'Windows' && matrix.dependency_mode != 'no_deps'
shell: bash
Expand Down
125 changes: 82 additions & 43 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -1,63 +1,102 @@
name: Test Coverage

on: [pull_request, workflow_dispatch]
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

concurrency:
group: coverage-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build:
coverage:
name: Report Test Coverage
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: read
id-token: write

steps:
- uses: actions/checkout@v6
with:
submodules: true

- name: Install vcpkg (Linux/macOS)
if: runner.os != 'Windows'
shell: bash
- name: Cache vcpkg binaries
uses: actions/cache@v5
with:
path: ~/.cache/vcpkg/archives
key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }}
restore-keys: vcpkg-${{ runner.os }}-

- name: Install vcpkg
run: |
git clone https://github.com/microsoft/vcpkg.git "$HOME/vcpkg"
"$HOME/vcpkg/bootstrap-vcpkg.sh" -disableMetrics
echo "VCPKG_ROOT=$HOME/vcpkg" >> "$GITHUB_ENV"
echo "$HOME/vcpkg" >> "$GITHUB_PATH"

- name: Install vcpkg (Windows)
if: runner.os == 'Windows'
shell: pwsh
- name: Build and test with coverage instrumentation
id: build
run: |
python3 setup.py --platform linux_x64 --compiler gcc --build --test --coverage --no_cov_report
mkdir -p coverage

- name: Generate coverage report
uses: threeal/gcovr-action@v1.2.0
with:
fail-under-line: 60
fail-under-branch: 50
fail-under-function: 80
print-summary: true
txt-out: coverage/coverage.txt
html-out: coverage/index.html
html-details: true
cobertura-out: coverage/cobertura.xml

- name: Publish coverage summary
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
run: |
git clone https://github.com/microsoft/vcpkg.git "$env:USERPROFILE\vcpkg"
& "$env:USERPROFILE\vcpkg\bootstrap-vcpkg.bat" -disableMetrics
echo "VCPKG_ROOT=$env:USERPROFILE\vcpkg" >> $env:GITHUB_ENV
echo "$env:USERPROFILE\vcpkg" >> $env:GITHUB_PATH

- name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build

- name: Setup LCOV
uses: hrishikesh-kadam/setup-lcov@v1

- name: Configure CMake
shell: bash
working-directory: ${{github.workspace}}/build
run: cmake -DENABLE_COVERAGE=ON ..

- name: Build
working-directory: ${{github.workspace}}/build
shell: bash
run: cmake --build .

- name: Prepare coverage data
working-directory: ${{github.workspace}}/build
shell: bash
run: cmake --build . --target cov_data

- name: Report code coverage
uses: zgosalvez/github-actions-report-lcov@v4
{
echo '```'
cat coverage/coverage.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

- name: Upload to Codecov
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
uses: codecov/codecov-action@v5
with:
files: coverage/cobertura.xml
use_oidc: true

- name: Upload coverage report
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
uses: actions/upload-artifact@v7
with:
coverage-files: build/cov.info.cleaned
minimum-coverage: 30
artifact-name: code-coverage-report
github-token: ${{ secrets.GITHUB_TOKEN }}
working-directory: ${{github.workspace}}
update-comment: true
name: code-coverage-report
path: coverage/

- name: Upload Pages artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v4
with:
path: coverage/

publish-pages:
name: Publish Report to GitHub Pages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: coverage
runs-on: ubuntu-24.04
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

steps:
- id: deployment
uses: actions/deploy-pages@v4
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@
[Bb]uild_old/
/gtest_build
.DS_Store
*.pyc
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 5.4.1

### Fixed

- **Remote configs caching** — fixed cached configs being re-downloaded on every launch instead of reused across sessions.
- **Remote configs listeners** — fixed a possible crash when adding or removing a listener during session start.
- **Custom fields** — fixed one invalid field dropping all other custom fields on the event.
- **Resource leaks** — fixed curl handle and header list leaks, and a per-event socket leak on Linux.

## 5.4.0

### Added
Expand Down
95 changes: 7 additions & 88 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ include("create_source_groups_macro")
include("eval_condition_macro")

# --------------------------- Options --------------------------- #
option(ENABLE_COVERAGE "Enable code coverage reporting" OFF)
option(ENABLE_COVERAGE "Build with code coverage instrumentation" OFF)
option(GA_SHARED_LIB "Build GA as a shared library" OFF)
option(GA_UWP_BUILD "Build GA for UWP (if targeting windows)" OFF)
option(GA_BUILD_SAMPLE "Builds the GA Sample app" ON)
Expand Down Expand Up @@ -275,93 +275,12 @@ else()
message(STATUS "Skipping unit tests (not available for shared library builds)")
endif()

# --------------------------- Code Coverage Setup --------------------------- #
# --------------------------- Code Coverage Instrumentation --------------------------- #

# Coverage requires tests, which are only available for static library builds
if (ENABLE_COVERAGE AND NOT GA_SHARED_LIB)
find_program(GCOV_PATH gcov)
if (NOT GCOV_PATH)
message(WARNING "program gcov not found")
endif()

find_program(LCOV_PATH lcov)
if (NOT LCOV_PATH)
message(WARNING "program lcov not found")
endif()

find_program(GENHTML_PATH genhtml)
if (NOT GENHTML_PATH)
message(WARNING "program genhtml not found")
endif()

if (LCOV_PATH AND GCOV_PATH)

target_compile_options(
GameAnalytics
PRIVATE
-g -O0 -fprofile-arcs -ftest-coverage
)

target_link_libraries(
GameAnalytics PRIVATE -fprofile-arcs -ftest-coverage
)

set(covname cov)

add_custom_target(cov_data
# Cleanup lcov
COMMENT "Resetting code coverage counters to zero."
${LCOV_PATH} --directory . --zerocounters

# Run tests
COMMAND GameAnalyticsUnitTests

# Capturing lcov counters and generating report

COMMAND echo "Processing code coverage counters and generating report."

COMMAND ${LCOV_PATH} --directory . --capture --output-file ${covname}.info --branch-coverage --rc geninfo_unexecuted_blocks=1 --rc no_exception_branch=1

COMMAND echo "Removing unwanted files from coverage report."

COMMAND ${LCOV_PATH} --remove ${covname}.info
'${CMAKE_SOURCE_DIR}/source/dependencies/*'
'${CMAKE_SOURCE_DIR}/test/*'
'/usr/*'
'/Applications/Xcode.app/*'
--output-file ${covname}.info.cleaned
--ignore-errors unused

COMMAND echo "Finished processing code coverage counters and generating report."
)

if (GENHTML_PATH)
add_custom_target(cov

# Cleanup lcov
${LCOV_PATH} --directory . --zerocounters

# Run tests
COMMAND GameAnalyticsUnitTests

# Capturing lcov counters and generating report
COMMAND ${LCOV_PATH} --directory . --capture --output-file ${covname}.info --rc lcov_branch_coverage=1 --rc derive_function_end_line=0
COMMAND ${LCOV_PATH} --remove ${covname}.info
'${CMAKE_SOURCE_DIR}/source/dependencies/*'
'/usr/*'
--output-file ${covname}.info.cleaned
--rc lcov_branch_coverage=1
--rc derive_function_end_line=0
COMMAND ${GENHTML_PATH} -o ${covname} ${covname}.info.cleaned --rc lcov_branch_coverage=1 --rc derive_function_end_line=0
COMMAND ${CMAKE_COMMAND} -E remove ${covname}.info ${covname}.info.cleaned

COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
)
else()
message(WARNING "unable to generate coverage report: missing genhtml")
endif()

else()
message(WARNING "unable to add coverage targets: missing coverage tools")
if(ENABLE_COVERAGE)
if(GA_SHARED_LIB)
message(FATAL_ERROR "ENABLE_COVERAGE requires a static library build (coverage is measured through the unit tests)")
endif()
target_compile_options(GameAnalytics PRIVATE -g -O0 --coverage -fprofile-update=atomic)
target_link_options(GameAnalytics PUBLIC --coverage)
Comment on lines +284 to +285
endif()
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
GA-SDK-CPP
==========

[![codecov](https://codecov.io/gh/GameAnalytics/gameanalytics-sdk-cpp/branch/main/graph/badge.svg)](https://codecov.io/gh/GameAnalytics/gameanalytics-sdk-cpp)

GameAnalytics C++ SDK

Documentation can be found [here](https://gameanalytics.com/docs/cpp-sdk).
Expand Down Expand Up @@ -42,7 +44,7 @@ python setup.py --platform {linux_x64,linux_x86,osx,win32,win64,uwp} [--cfg {Rel
| `--shared` | — | Build a shared library (`.dll`/`.so`/`.dylib`) instead of a static library |
| `--build` | — | Execute the build step |
| `--test` | — | Execute the test step (not available with `--shared`) |
| `--coverage` | — | Generate code coverage report (not available with `--shared`) |
| `--coverage` | — | Build with coverage instrumentation and generate an HTML report in `build/coverage/` (requires `--build --test`; not available with `--shared`; needs [gcovr](https://gcovr.com) installed) |

Comment on lines +47 to 48
#### Examples

Expand Down
4 changes: 4 additions & 0 deletions gcovr.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
filter = source/gameanalytics/
filter = include/GameAnalytics/
exclude = source/gameanalytics/Platform/
exclude-throw-branches = yes
30 changes: 23 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import shutil
import glob
import platform as Platform
import webbrowser

def run_command(command, shell=True, cwd=None):
if os.name == 'nt': # Check if the OS is Windows
Expand Down Expand Up @@ -34,7 +35,8 @@ def main():
parser.add_argument('--shared', action='store_true', help='Build shared library instead of static')
parser.add_argument('--build', action='store_true', help='Execute the build step')
parser.add_argument('--test', action='store_true', help='Execute the test step')
parser.add_argument('--coverage', action='store_true', help='Generate code coverage report')
parser.add_argument('--coverage', action='store_true', help='Build with coverage instrumentation and generate a coverage report')
parser.add_argument('--no_cov_report', action='store_true', help='Skip the local coverage report generation (used on CI where gcovr runs separately)')
parser.add_argument('--no_vcpkg', action='store_true', help='Do not download vcpkg packages')
parser.add_argument('--no_curl', action='store_true', help='Compile the SDK without CURL (you will need to provide a custom HTTP client implementation)')

Expand All @@ -44,10 +46,15 @@ def main():
if args.compiler and not args.platform.startswith('linux'):
parser.error('--compiler can only be used with Linux platforms')

# Validate coverage is not used with shared library
if args.coverage and args.shared:
parser.error('--coverage cannot be used with --shared (coverage requires tests which need static library)')

if args.coverage and not (args.build and args.test):
parser.error('--coverage requires --build and --test')

if args.no_cov_report and not args.coverage:
parser.error('--no_cov_report requires --coverage')

# Get compiler configuration for this platform (single compiler, like cmake.yml)
compiler_config = get_compiler_for_platform(args.platform, args.compiler)
c_compiler = compiler_config.get('c', '')
Expand Down Expand Up @@ -123,7 +130,7 @@ def main():
cmake_command += f' -DPLATFORM:STRING={args.platform}'
if args.coverage:
cmake_command += ' -DENABLE_COVERAGE=ON'

run_command(cmake_command)

# Build
Expand All @@ -138,10 +145,19 @@ def main():
else:
exit(0)

# Code Coverage
if args.coverage:
# Prepare coverage data
run_command(f'cmake --build {build_output_dir} --target cov', cwd=build_output_dir)
# Code Coverage Report
if args.coverage and not args.no_cov_report:
coverage_dir = os.path.join(build_output_dir, 'coverage')
os.makedirs(coverage_dir, exist_ok=True)
report_path = os.path.join(coverage_dir, 'index.html')
gcovr_command = f'gcovr --print-summary --html-details {report_path}'
if args.platform == 'osx':
gcovr_command += ' --gcov-executable "xcrun llvm-cov gcov"'
elif cxx_compiler == 'clang++':
gcovr_command += ' --gcov-executable "llvm-cov gcov"'
run_command(gcovr_command)
print(f"\nCoverage report: {report_path}\n")
webbrowser.open(f'file://{report_path}')

# Package Build Artifacts
package_dir = os.path.join(build_output_dir, 'package')
Expand Down
2 changes: 1 addition & 1 deletion source/gameanalytics/GACommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ namespace gameanalytics
class GAState;
}

constexpr const char* GA_VERSION_STR = "cpp 5.4.0";
constexpr const char* GA_VERSION_STR = "cpp 5.4.1";

constexpr int MAX_CUSTOM_FIELDS_COUNT = 50;
constexpr int MAX_CUSTOM_FIELDS_KEY_LENGTH = 64;
Expand Down
Loading
Loading