diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 06bce4267..2d50517d8 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -17,13 +17,18 @@ on: - main - dev + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build: runs-on: windows-latest name: Build for Android steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: false - name: Update submodules @@ -32,7 +37,7 @@ jobs: git config --global submodule.lib/modules.update none git -c protocol.version=2 submodule update --init --force --depth=1 - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: distribution: 'adopt' java-version: '17' @@ -40,14 +45,13 @@ jobs: # Workaround for: 'Unable to decrypt local Maven settings credentials' run: rm $Env:USERPROFILE\.m2\settings.xml - name: Setup Android SDK - uses: android-actions/setup-android@v2 + uses: android-actions/setup-android@v3 - name: Install NDK run: | java -version gci env:* | sort-object name - new-item "C:\Users\runneradmin\.android\repositories.cfg" -ItemType "file" - echo yes | .\sdkmanager.bat "ndk-bundle" "cmake;3.10.2.4988404" "ndk;27.0.12077973" --sdk_root=$Env:ANDROID_SDK_ROOT - working-directory: ${{ env.ANDROID_SDK_ROOT }}\cmdline-tools\7.0\bin + new-item "$Env:USERPROFILE\.android\repositories.cfg" -ItemType "file" + echo yes | sdkmanager "ndk-bundle" "cmake;3.10.2.4988404" "ndk;27.0.12077973" --sdk_root=$Env:ANDROID_SDK_ROOT - name: Chocolatey run: | choco install --no-progress -y ninja diff --git a/.github/workflows/build-ios-mac.yml b/.github/workflows/build-ios-mac.yml index 80c5f981d..be9ec53f9 100644 --- a/.github/workflows/build-ios-mac.yml +++ b/.github/workflows/build-ios-mac.yml @@ -19,36 +19,42 @@ on: schedule: - cron: 0 2 * * 1-5 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build: strategy: matrix: - os: [macos-13, macos-15] + os: [macos-14, macos-15] config: [release, debug] simulator: ["'iPhone 15'", "'iPad Pro (11-inch) (4th generation)'", "'iPhone 16'", "'iPad Air 11-inch (M2)'"] exclude: - - os: macos-13 + - os: macos-14 simulator: "'iPhone 16'" - - os: macos-13 + - os: macos-14 simulator: "'iPad Air 11-inch (M2)'" - os: macos-15 simulator: "'iPhone 15'" - os: macos-15 simulator: "'iPad Pro (11-inch) (4th generation)'" runs-on: ${{ matrix.os }} + timeout-minutes: 30 env: CMAKE_POLICY_VERSION_MINIMUM: "3.5" steps: - name: Grant write permissions to /usr/local run: | sudo chown -R $USER:staff /usr/local - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: 'true' continue-on-error: true - name: build run: | - if [[ "${{ matrix.os }}" == "macos-13" ]]; then + if [[ "${{ matrix.os }}" == "macos-14" ]]; then export IOS_DEPLOYMENT_TARGET=13.0; elif [[ "${{ matrix.os }}" == "macos-15" ]]; then export IOS_DEPLOYMENT_TARGET=15.0; diff --git a/.github/workflows/build-posix-latest.yml b/.github/workflows/build-posix-latest.yml index 2271a459b..657caa6e0 100644 --- a/.github/workflows/build-posix-latest.yml +++ b/.github/workflows/build-posix-latest.yml @@ -19,6 +19,11 @@ on: schedule: - cron: 0 2 * * 1-5 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build: @@ -32,7 +37,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 continue-on-error: true - name: Test ${{ matrix.os }} ${{ matrix.config }} run: ./build-tests.sh ${{ matrix.config }} diff --git a/.github/workflows/build-ubuntu-2204.yml b/.github/workflows/build-ubuntu-2204.yml index e8f456bbc..e0ea03ac5 100644 --- a/.github/workflows/build-ubuntu-2204.yml +++ b/.github/workflows/build-ubuntu-2204.yml @@ -19,6 +19,11 @@ on: schedule: - cron: 0 2 * * 1-5 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build: @@ -32,7 +37,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 continue-on-error: true - name: Test ${{ matrix.os }} ${{ matrix.config }} run: ./build-tests.sh ${{ matrix.config }} \ No newline at end of file diff --git a/.github/workflows/build-windows-clang.yaml.off b/.github/workflows/build-windows-clang.yaml.off index 9f2c790ea..2621613c1 100644 --- a/.github/workflows/build-windows-clang.yaml.off +++ b/.github/workflows/build-windows-clang.yaml.off @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 continue-on-error: true - name: Setup Tools diff --git a/.github/workflows/build-windows-vs2017.yaml.off b/.github/workflows/build-windows-vs2017.yaml.off index 1db4607db..a2d22827b 100644 --- a/.github/workflows/build-windows-vs2017.yaml.off +++ b/.github/workflows/build-windows-vs2017.yaml.off @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 continue-on-error: true - name: Setup Tools diff --git a/.github/workflows/build-windows-vs2022.yaml b/.github/workflows/build-windows-vs2022.yaml index a8a18394e..20605b442 100644 --- a/.github/workflows/build-windows-vs2022.yaml +++ b/.github/workflows/build-windows-vs2022.yaml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 continue-on-error: true - name: Build diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 503cae673..3a55f2bc5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -14,6 +14,11 @@ on: schedule: - cron: '0 8 * * 1' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: analyze: name: Analyze @@ -34,7 +39,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 continue-on-error: true # Initializes the CodeQL tools for scanning. @@ -87,7 +92,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 continue-on-error: true - name: Update submodules @@ -102,21 +107,20 @@ jobs: languages: java - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: distribution: 'adopt' java-version: '17' - name: Remove default github maven configuration run: rm $Env:USERPROFILE\.m2\settings.xml - name: Setup Android SDK - uses: android-actions/setup-android@v2 + uses: android-actions/setup-android@v3 - name: Install NDK run: | java -version gci env:* | sort-object name - new-item "C:\Users\runneradmin\.android\repositories.cfg" -ItemType "file" - echo yes | .\sdkmanager.bat "ndk-bundle" "cmake;3.10.2.4988404" "ndk;27.0.12077973" --sdk_root=$Env:ANDROID_SDK_ROOT - working-directory: ${{ env.ANDROID_SDK_ROOT }}\cmdline-tools\7.0\bin + new-item "$Env:USERPROFILE\.android\repositories.cfg" -ItemType "file" + echo yes | sdkmanager "ndk-bundle" "cmake;3.10.2.4988404" "ndk;27.0.12077973" --sdk_root=$Env:ANDROID_SDK_ROOT - name: Chocolatey run: | choco install --no-progress -y ninja diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index 69b136fa1..912594c61 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -6,6 +6,11 @@ on: pull_request: branches: [ master, main ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build: runs-on: ubuntu-latest @@ -13,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 continue-on-error: true - name: install misspell diff --git a/.github/workflows/test-android-mac.yml.off b/.github/workflows/test-android-mac.yml.off index 56b87579a..96e17c4a5 100644 --- a/.github/workflows/test-android-mac.yml.off +++ b/.github/workflows/test-android-mac.yml.off @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: submodules: true depth: 1 @@ -42,7 +42,7 @@ jobs: script: ./testandlog - name: Upload if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: logcat path: ./lib/android_build/logcat.txt \ No newline at end of file diff --git a/.github/workflows/test-win-latest.yml b/.github/workflows/test-win-latest.yml index 760c88fb0..19b20ef53 100644 --- a/.github/workflows/test-win-latest.yml +++ b/.github/workflows/test-win-latest.yml @@ -19,6 +19,11 @@ on: schedule: - cron: 0 2 * * 1-5 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: test: name: Test on Windows ${{ matrix.arch }}-${{ matrix.build }} @@ -32,13 +37,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 continue-on-error: true - name: setup-msbuild - uses: microsoft/setup-msbuild@v1.1 + uses: microsoft/setup-msbuild@v2 with: - vs-version: '[16,)' + vs-version: '[17,)' - name: Test ${{ matrix.arch }} ${{ matrix.build }} shell: cmd diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 281bf4b1f..dec4558cd 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,11 +1,19 @@ version: 2 +build: + os: ubuntu-24.04 + tools: + python: "3.12" + apt_packages: + - doxygen + - graphviz + submodules: exclude: all python: - install: - - requirements: docs/public/requirements.txt + install: + - requirements: docs/public/requirements.txt sphinx: - configuration: docs/public/conf.py + configuration: docs/public/conf.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 959134a61..3117d4b29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.1.0) -project(MSTelemetry) +project(MSTelemetry LANGUAGES C CXX) # Set installation prefix for macOS and Linux if(UNIX AND NOT DEFINED CMAKE_INSTALL_PREFIX) @@ -25,7 +25,7 @@ endif() # Enable ARC for obj-c on Apple if(APPLE) - message("-- BUILD_IOS: ${BUILD_IOS}") + message(STATUS "BUILD_IOS: ${BUILD_IOS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fobjc-arc") # iOS build options @@ -77,9 +77,9 @@ if(APPLE) OUTPUT_VARIABLE CMAKE_OSX_SYSROOT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - message("-- CMAKE_OSX_SYSROOT ${CMAKE_OSX_SYSROOT}") - message("-- ARCHITECTURE: ${CMAKE_SYSTEM_PROCESSOR}") - message("-- PLATFORM: ${IOS_PLATFORM}") + message(STATUS "CMAKE_OSX_SYSROOT ${CMAKE_OSX_SYSROOT}") + message(STATUS "ARCHITECTURE: ${CMAKE_SYSTEM_PROCESSOR}") + message(STATUS "PLATFORM: ${IOS_PLATFORM}") else() if(${MAC_ARCH} STREQUAL "x86_64") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch x86_64") @@ -99,26 +99,29 @@ if(APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch x86_64 -arch arm64") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch x86_64 -arch arm64") endif() - message("-- MAC_ARCH: ${MAC_ARCH}") + message(STATUS "MAC_ARCH: ${MAC_ARCH}") endif() endif() -message("-- CMAKE_SYSTEM_INFO_FILE: ${CMAKE_SYSTEM_INFO_FILE}") -message("-- CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") -message("-- CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") -message("-- CMAKE_SYSTEM: ${CMAKE_SYSTEM}") -message("-- CMAKE_SYSTEM_VERSION: ${CMAKE_SYSTEM_VERSION}") -message("-- CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") -message("-- TARGET_ARCH: ${TARGET_ARCH}") -message("-- CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") +message(STATUS "CMAKE_SYSTEM_INFO_FILE: ${CMAKE_SYSTEM_INFO_FILE}") +message(STATUS "CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") +message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") +message(STATUS "CMAKE_SYSTEM: ${CMAKE_SYSTEM}") +message(STATUS "CMAKE_SYSTEM_VERSION: ${CMAKE_SYSTEM_VERSION}") +message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") +message(STATUS "TARGET_ARCH: ${TARGET_ARCH}") +message(STATUS "CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") include(tools/ParseOsRelease.cmake) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") -set(WARN_FLAGS "/W4 /WX") + set(WARN_FLAGS "/W4 /WX") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + # -Wno-unknown-warning-option is Clang-only, omitted here + set(WARN_FLAGS "-Wall -Werror -Wextra -Wno-unused-parameter -Wno-unused-but-set-variable") else() -# No -pedantic -Wno-extra-semi -Wno-gnu-zero-variadic-macro-arguments -set(WARN_FLAGS "-Wall -Werror -Wextra -Wno-unused-parameter -Wno-unknown-warning-option -Wno-unused-but-set-variable") + # No -pedantic -Wno-extra-semi -Wno-gnu-zero-variadic-macro-arguments + set(WARN_FLAGS "-Wall -Werror -Wextra -Wno-unused-parameter -Wno-unknown-warning-option -Wno-unused-but-set-variable -Wno-nan-infinity-disabled") endif() if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") @@ -144,12 +147,12 @@ set(DBG_FLAGS "-ggdb -gdwarf-2 -O0 ${WARN_FLAGS} -fno-builtin-malloc -fno-built if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") #TODO: -fno-rtti - message("Building Release ...") + message(STATUS "Building Release ...") set(CMAKE_C_FLAGS "$ENV{CFLAGS} ${CMAKE_C_FLAGS} -std=c11 ${REL_FLAGS}") set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} ${CMAKE_CXX_FLAGS} -std=c++11 ${REL_FLAGS}") else() set(USE_TCMALLOC 1) - message("Building Debug ...") + message(STATUS "Building Debug ...") include(tools/FindTcmalloc.cmake) set(CMAKE_C_FLAGS "$ENV{CFLAGS} ${CMAKE_C_FLAGS} -std=c11 ${DBG_FLAGS}") set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} ${CMAKE_CXX_FLAGS} -std=c++11 ${DBG_FLAGS}") @@ -190,7 +193,7 @@ endif() set(PAL_IMPLEMENTATION ${DEFAULT_PAL_IMPLEMENTATION}) -message(STATUS "-- PAL implementation: ${PAL_IMPLEMENTATION}") +message(STATUS "PAL implementation: ${PAL_IMPLEMENTATION}") string(TOUPPER ${PAL_IMPLEMENTATION} PAL_IMPLEMENTATION_UPPER) add_definitions(-DMATSDK_PAL_${PAL_IMPLEMENTATION_UPPER}=1) @@ -222,7 +225,7 @@ add_definitions(-DNOMINMAX) set(SDK_VERSION_PREFIX "EVT") add_definitions("-DMATSDK_VERSION_PREFIX=\"${SDK_VERSION_PREFIX}\"") -set(MATSDK_API_VERSION "3.4") +set(MATSDK_API_VERSION "3.10") string(TIMESTAMP DAYNUMBER "%j") string(REGEX REPLACE "^00" "" DAYNUMBER ${DAYNUMBER}) string(REGEX REPLACE "^0" "" DAYNUMBER ${DAYNUMBER}) @@ -238,7 +241,7 @@ else() set(MATSDK_BUILD_VERSION ${MATSDK_API_VERSION}.${DAYNUMBER}.0) endif() -message(STATUS "-- SDK version: ${SDK_VERSION_PREFIX}-${MATSDK_BUILD_VERSION}") +message(STATUS "SDK version: ${SDK_VERSION_PREFIX}-${MATSDK_BUILD_VERSION}") ################################################################################################ # HTTP stack section @@ -286,9 +289,9 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") endif() if(BUILD_UNIT_TESTS OR BUILD_FUNC_TESTS) - message("Adding gtest") + message(STATUS "Adding gtest") add_library(gtest STATIC IMPORTED GLOBAL) - message("Adding gmock") + message(STATUS "Adding gmock") add_library(gmock STATIC IMPORTED GLOBAL) endif() @@ -320,7 +323,7 @@ if(BUILD_LIBRARY) endif() if(BUILD_UNIT_TESTS OR BUILD_FUNC_TESTS) - message("Building tests") + message(STATUS "Building tests") enable_testing() add_subdirectory(tests) endif() diff --git a/README.md b/README.md index c4da1e1c6..3ddcbb580 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,9 @@ Other resources to learn how to setup the build system: * **Supported** - these platforms are known to work well with the SDK in production. * **Covered by CI** - these platforms are tested as part of CI. +* For iOS simulator, CI covers representative supported simulator + configurations on the current macOS runner images rather than every + supported iOS 12+ runtime. ## Test diff --git a/Solutions/before.targets b/Solutions/before.targets index 63a526c90..052e5d77f 100644 --- a/Solutions/before.targets +++ b/Solutions/before.targets @@ -3,12 +3,12 @@ $(SolutionDir)\..\third_party\krabsetw\krabs;$(CustomIncludePath) - v141 - v142 - v143 - + + v145 + v143 + v142 + v141 v141 - $(PlatformToolset) $([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0')) $(LatestTargetPlatformVersion) diff --git a/Solutions/build.MIP.props b/Solutions/build.MIP.props index 9bd6e6d91..2419c4c36 100644 --- a/Solutions/build.MIP.props +++ b/Solutions/build.MIP.props @@ -1,20 +1,24 @@ - - - - - %(PreprocessorDefinitions);CONFIG_CUSTOM_H="config-MIP.h";MATSDK_SHARED_LIB=1; - - - ucrtbased.dll; %(DelayLoadDLLs) - - - - true - v141 - 14.1 - - - mip_ClientTelemetry - - - + + + + + %(PreprocessorDefinitions);CONFIG_CUSTOM_H="config-MIP.h";MATSDK_SHARED_LIB=1; + + + ucrtbased.dll; %(DelayLoadDLLs) + + + + true + $(DefaultPlatformToolset) + v145 + v143 + v142 + v141 + v141 + + + mip_ClientTelemetry + + + diff --git a/build-tests-ios.sh b/build-tests-ios.sh index bf29a50b3..3e4a40f46 100755 --- a/build-tests-ios.sh +++ b/build-tests-ios.sh @@ -7,14 +7,67 @@ set -e ./build-ios.sh ${SKU} -# dyld_info /Users/runner/work/cpp_client_telemetry/cpp_client_telemetry/out/lib/libmat.a - cd tests/unittests xcrun simctl list devices available echo 'End of xcrun simctl list devices available' -xcodebuild test -scheme iOSUnitTests -destination "platform=iOS Simulator,name=$SIMULATOR" +# Resolve simulator UUID from simctl JSON using an exact name match. +# If the same device name exists across multiple iOS runtimes, pick the +# newest runtime and fail if that runtime still contains duplicate matches. +SIM_MATCH=$( + xcrun simctl list devices available --json | python3 -c ' +import json +import re +import sys + +simulator_name = sys.argv[1] +devices_by_runtime = json.load(sys.stdin).get("devices", {}) +matches = [] + +for runtime, devices in devices_by_runtime.items(): + if not runtime.startswith("com.apple.CoreSimulator.SimRuntime.iOS-"): + continue + + match = re.search(r"iOS-(\d+)(?:-(\d+))?$", runtime) + if match is None: + continue + + version = tuple(int(part) for part in match.groups(default="0")) + runtime_label = "iOS " + ".".join(str(part) for part in version) + + for device in devices: + if device.get("name") == simulator_name and device.get("isAvailable", True): + matches.append((version, runtime_label, device["udid"])) + +if not matches: + print(f"ERROR: No available simulator found for {simulator_name!r}", file=sys.stderr) + raise SystemExit(1) + +newest_version = max(version for version, _, _ in matches) +newest_matches = [(runtime_label, udid) for version, runtime_label, udid in matches if version == newest_version] + +if len(newest_matches) != 1: + print(f"ERROR: Multiple available simulators found for {simulator_name!r} in newest runtime:", file=sys.stderr) + for runtime_label, udid in newest_matches: + print(f" - {runtime_label}: {udid}", file=sys.stderr) + raise SystemExit(1) + +runtime_label, udid = newest_matches[0] +print(f"{udid}|{runtime_label}") +' "$SIMULATOR" +) +SIM_ID=${SIM_MATCH%%|*} +SIM_RUNTIME=${SIM_MATCH#*|} + +if [ -z "$SIM_ID" ]; then + echo "ERROR: No available simulator found for '$SIMULATOR'" + exit 1 +fi + +echo "Using simulator: $SIMULATOR ($SIM_RUNTIME, id=$SIM_ID)" + +xcodebuild test -scheme iOSUnitTests -destination "id=$SIM_ID" cd ../functests -xcodebuild test -scheme iOSFuncTests -destination "platform=iOS Simulator,name=$SIMULATOR" +xcodebuild test -scheme iOSFuncTests -destination "id=$SIM_ID" diff --git a/docs/cpp-start-ios.md b/docs/cpp-start-ios.md index 35313e7fa..ad80866e0 100644 --- a/docs/cpp-start-ios.md +++ b/docs/cpp-start-ios.md @@ -16,12 +16,38 @@ Run `build-ios.sh [clean] [release|debug]` script in the root folder of the sour Run `build-ios.sh [clean] [release|debug] [arm64|arm64e]`. +### Run iOS tests + +Run `./build-tests-ios.sh [release|debug] ""`. + +The script requires `python3` on `PATH` to parse `simctl --json` when resolving the simulator name. + +Example: + +```sh +./build-tests-ios.sh release "iPhone 17" +``` + +Use a simulator name returned by `xcrun simctl list devices available`. +The script resolves an exact device name from `simctl --json`, prefers the +newest installed iOS runtime for that device name, and fails if that newest +runtime still contains multiple matches. + +If Xcode reports that the requested simulator runtime is missing, install it +from Xcode > Settings > Components or run +`xcodebuild -downloadPlatform iOS -architectureVariant arm64`. + ## 3. Integrate the SDK into your C++ project -SDK package contains headers and library installed at the following locations: +SDK package contains headers and library installed at the following locations +by default: * Headers: /usr/local/include/mat -* Library: /usr/local/lib/${arch}/libmat.a +* Library: /usr/local/lib/libmat.a + +If you set a custom install prefix via +`CMAKE_OPTS="-DCMAKE_INSTALL_PREFIX=/path/to/install"`, the SDK is installed +under `/include/mat` and `/lib/libmat.a`. 1DS SDK is built using cmake, but you can explore building it with any other build system of your choice. diff --git a/docs/public/conf.py b/docs/public/conf.py index d38fa3f7b..1d29ebe99 100644 --- a/docs/public/conf.py +++ b/docs/public/conf.py @@ -16,7 +16,7 @@ # -- Project information ----------------------------------------------------- -project = 'Microsoft C++ Client Telemetry SDK"' +project = 'Microsoft C++ Client Telemetry SDK' copyright = 'Microsoft Corporation' author = 'Microsoft Corporation' @@ -28,8 +28,6 @@ # This is necessary so the readthedocs build works. It doesn't invoke the # Makefile, but just runs sphinx on this conf.py. import os -import shutil -import subprocess if not os.path.exists('doxyoutput'): os.makedirs('doxyoutput') @@ -61,7 +59,7 @@ primary_domain = "cpp" -higlight_language = "cpp" +highlight_language = "cpp" # Add any paths that contain templates here, relative to this directory. @@ -78,10 +76,9 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -#html_theme = "furo" -html_theme = "sphinx_rtd_theme" +html_theme = "furo" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +html_static_path = [] diff --git a/docs/public/requirements.txt b/docs/public/requirements.txt index 7419647ac..92d9b3915 100644 --- a/docs/public/requirements.txt +++ b/docs/public/requirements.txt @@ -1,3 +1,4 @@ +sphinx<10 breathe exhale -furo \ No newline at end of file +furo diff --git a/examples/cpp/SampleCpp/SampleCpp.vcxproj b/examples/cpp/SampleCpp/SampleCpp.vcxproj index 500d966f6..ffa49633a 100644 --- a/examples/cpp/SampleCpp/SampleCpp.vcxproj +++ b/examples/cpp/SampleCpp/SampleCpp.vcxproj @@ -1080,7 +1080,6 @@ - diff --git a/examples/cpp/SampleCpp/SampleCpp.vcxproj.filters b/examples/cpp/SampleCpp/SampleCpp.vcxproj.filters index 9a605a027..ae87cc1f8 100644 --- a/examples/cpp/SampleCpp/SampleCpp.vcxproj.filters +++ b/examples/cpp/SampleCpp/SampleCpp.vcxproj.filters @@ -15,9 +15,6 @@ - - Header Files - Source Files diff --git a/examples/cpp/SampleCppMini/SampleCppMini.vcxproj b/examples/cpp/SampleCppMini/SampleCppMini.vcxproj index 88bca8d6d..424394f7e 100644 --- a/examples/cpp/SampleCppMini/SampleCppMini.vcxproj +++ b/examples/cpp/SampleCppMini/SampleCppMini.vcxproj @@ -1542,7 +1542,6 @@ - diff --git a/examples/cpp/SampleCppMini/SampleCppMini.vcxproj.filters b/examples/cpp/SampleCppMini/SampleCppMini.vcxproj.filters index ad3de7523..2df19ab39 100644 --- a/examples/cpp/SampleCppMini/SampleCppMini.vcxproj.filters +++ b/examples/cpp/SampleCppMini/SampleCppMini.vcxproj.filters @@ -14,11 +14,6 @@ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - Header Files - - Source Files diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a39e65b98..8824427b4 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,7 +1,7 @@ # Honor visibility properties for all target types cmake_policy(SET CMP0063 NEW) -include_directories( . ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include/public ${CMAKE_CURRENT_SOURCE_DIR}/include/public ${CMAKE_CURRENT_SOURCE_DIR}/include/mat ${CMAKE_CURRENT_SOURCE_DIR}/pal ${CMAKE_CURRENT_SOURCE_DIR}/utils ${CMAKE_CURRENT_SOURCE_DIR}/modules/exp ${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard ${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/Reachability ${CMAKE_CURRENT_SOURCE_DIR}/modules/cds ${CMAKE_CURRENT_SOURCE_DIR}/modules/signals ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer /usr/local/include ) +include_directories( . ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include/public ${CMAKE_CURRENT_SOURCE_DIR}/include/mat ${CMAKE_CURRENT_SOURCE_DIR}/pal ${CMAKE_CURRENT_SOURCE_DIR}/utils ${CMAKE_CURRENT_SOURCE_DIR}/modules/exp ${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard ${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/Reachability ${CMAKE_CURRENT_SOURCE_DIR}/modules/cds ${CMAKE_CURRENT_SOURCE_DIR}/modules/signals ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer /usr/local/include ) set(SRCS decorators/BaseDecorator.cpp packager/BondSplicer.cpp @@ -60,7 +60,7 @@ if(BUILD_AZMON) include(modules/azmon/CMakeLists.txt OPTIONAL) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/exp/) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/exp/") list(APPEND SRCS modules/exp/afd/afdclient/AFDClientUtils.cpp modules/exp/afd/afdclient/AFDClient.cpp @@ -74,14 +74,14 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/exp/) ) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer/) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer/") list(APPEND SRCS modules/dataviewer/DefaultDataViewer.cpp modules/dataviewer/OnDisableNotificationCollection.cpp ) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/ AND BUILD_PRIVACYGUARD) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/" AND BUILD_PRIVACYGUARD) list(APPEND SRCS modules/privacyguard/PrivacyGuard.cpp modules/privacyguard/RegisteredFileTypes.cpp @@ -89,14 +89,14 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/ AND BUILD_PRIVACYGUA ) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector/ AND BUILD_LIVEEVENTINSPECTOR) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector/" AND BUILD_LIVEEVENTINSPECTOR) list(APPEND SRCS modules/liveeventinspector/LiveEventInspector.cpp modules/liveeventinspector/LiveEventInspector.hpp ) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/cds/ AND BUILD_CDS) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/cds/" AND BUILD_CDS) add_definitions(-DHAVE_MAT_CDS) list(APPEND SRCS modules/cds/CdsFactory.hpp @@ -104,14 +104,14 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/cds/ AND BUILD_CDS) ) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/signals/ AND BUILD_SIGNALS) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/signals/" AND BUILD_SIGNALS) list(APPEND SRCS modules/signals/Signals.cpp modules/signals/SignalsEncoder.cpp ) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/ AND BUILD_SANITIZER) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/" AND BUILD_SANITIZER) list(APPEND SRCS modules/sanitizer/detectors/EmailAddressDetector.cpp modules/sanitizer/detectors/JwtDetector.cpp @@ -124,6 +124,15 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/ AND BUILD_SANITIZER) modules/sanitizer/SanitizerTrie.cpp modules/sanitizer/SanitizerTrieNode.cpp ) + # Suppress -Wreorder for Sanitizer.cpp only: the submodule declares + # m_sanitizerprovider before m_notificationEventName in Sanitizer.hpp + # but the constructor init list reverses that order. The proper fix + # belongs in the lib/modules submodule; this scopes the suppression + # until that is done. + if(NOT MSVC) + set_source_files_properties(modules/sanitizer/Sanitizer.cpp + PROPERTIES COMPILE_FLAGS "-Wno-reorder") + endif() endif() if(PAL_IMPLEMENTATION STREQUAL "CPP11") @@ -172,7 +181,7 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") ) endif() if(APPLE AND BUILD_OBJC_WRAPPER) - message("Include ObjC Wrappers") + message(STATUS "Include ObjC Wrappers") list(APPEND SRCS ../wrappers/obj-c/ODWLogger.mm ../wrappers/obj-c/ODWLogManager.mm @@ -180,19 +189,19 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") ../wrappers/obj-c/ODWLogConfiguration.mm ../wrappers/obj-c/ODWSemanticContext.mm ) - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer/) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer/") list(APPEND SRCS ../wrappers/obj-c/ODWDiagnosticDataViewer.mm ) endif() - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/ AND BUILD_PRIVACYGUARD) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/" AND BUILD_PRIVACYGUARD) list(APPEND SRCS ../wrappers/obj-c/ODWCommonDataContext.mm ../wrappers/obj-c/ODWPrivacyGuard.mm ../wrappers/obj-c/ODWPrivacyGuardInitConfig.mm ) endif() - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/ AND BUILD_SANITIZER) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/" AND BUILD_SANITIZER) list(APPEND SRCS ../wrappers/obj-c/ODWSanitizerInitConfig.mm ../wrappers/obj-c/ODWSanitizer.mm @@ -201,7 +210,7 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") endif() if(APPLE AND BUILD_SWIFT_WRAPPER) - message("Building Swift Wrappers") + message(STATUS "Building Swift Wrappers") # Run swift build for the Swift Wrappers Package string(TOLOWER ${CMAKE_BUILD_TYPE} LOWER_BUILD_TYPE) execute_process( @@ -213,9 +222,9 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") ) if(SWIFT_BUILD_RESULT EQUAL 0) - message("Swift Wrappers build succeeded!") + message(STATUS "Swift Wrappers build succeeded!") else() - message(FATAL_ERROR, "Swift build failed with error code: ${SWIFT_BUILD_RESULT}") + message(FATAL_ERROR "Swift build failed with error code: ${SWIFT_BUILD_RESULT}") endif() endif() @@ -238,7 +247,7 @@ remove_definitions(-D_MBCS) ) # UTC module - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/utc) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/utc") list(APPEND SRCS modules/utc/desktop/UtcHelpers.cpp modules/utc/UtcTelemetrySystem.cpp @@ -250,7 +259,7 @@ else() endif() # Filtering module -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/filter) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/filter") list(APPEND SRCS modules/filter/CompliantByDefaultEventFilterModule.cpp modules/filter/CompliantByDefaultFilterApi.cpp @@ -269,7 +278,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Linux") endif() if(BUILD_SHARED_LIBS STREQUAL "ON") - message("-- Building shared SDK library") + message(STATUS "Building shared SDK library") # include(FindCURL) # find_package(CURL REQUIRED) @@ -304,13 +313,19 @@ if(BUILD_SHARED_LIBS STREQUAL "ON") # target_link_libraries(mat PUBLIC libsqlite3 libcurl.a libz.a libssl.a libcrypto.a "${SQLITE_LIBRARY}" "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}" ) install(TARGETS mat EXPORT mat LIBRARY DESTINATION ${INSTALL_LIB_DIR}) else() - message("-- Building static SDK library") + message(STATUS "Building static SDK library") add_library(mat STATIC ${SRCS}) if(LINK_STATIC_DEPENDS) if(PAL_IMPLEMENTATION STREQUAL "WIN32") target_link_libraries(mat ${LIBS} "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}" ) else() add_library(sqlite3 STATIC IMPORTED GLOBAL) + find_library(SQLITE3_STATIC_LIB NAMES libsqlite3.a + PATHS /usr/local/lib /usr/local/opt/sqlite/lib /opt/homebrew/opt/sqlite/lib + NO_DEFAULT_PATH) + if(SQLITE3_STATIC_LIB) + set_target_properties(sqlite3 PROPERTIES IMPORTED_LOCATION ${SQLITE3_STATIC_LIB}) + endif() add_library(z STATIC IMPORTED GLOBAL) # # TODO: allow adding "${Tcmalloc_LIBRARIES}" to target_link_libraries for memory leak debugging @@ -321,10 +336,6 @@ else() install(TARGETS mat EXPORT mat ARCHIVE DESTINATION ${INSTALL_LIB_DIR}) endif() -message("-- Library will be installed to ${INSTALL_LIB_DIR}") +message(STATUS "Library will be installed to ${INSTALL_LIB_DIR}") -#if(PAL_IMPLEMENTATION STREQUAL "CPP11") -# #target_link_libraries(mat PUBLIC libcurl.a libz.a libssl.a libcrypto.a "${SQLITE_LIBRARY}" "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}" ) -# #target_link_libraries(mat PUBLIC libsqlite3.a libz.a ${LIBS} "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}" ) -#endif() diff --git a/lib/android_build/maesdk/src/main/cpp/CMakeLists.txt b/lib/android_build/maesdk/src/main/cpp/CMakeLists.txt index dbea0dc98..f53d5403a 100644 --- a/lib/android_build/maesdk/src/main/cpp/CMakeLists.txt +++ b/lib/android_build/maesdk/src/main/cpp/CMakeLists.txt @@ -45,6 +45,7 @@ set(SRCS ${SDK_ROOT}/lib/bond/BondSerializer.cpp ${SDK_ROOT}/lib/callbacks/DebugSource.cpp ${SDK_ROOT}/lib/compression/HttpDeflateCompression.cpp + ${SDK_ROOT}/lib/decoder/PayloadDecoder.cpp ${SDK_ROOT}/lib/decorators/BaseDecorator.cpp ${SDK_ROOT}/lib/filter/EventFilterCollection.cpp ${SDK_ROOT}/lib/http/HttpClientFactory.cpp diff --git a/lib/api/LogManagerImpl.cpp b/lib/api/LogManagerImpl.cpp index fac8fdbd5..2f0e8933d 100644 --- a/lib/api/LogManagerImpl.cpp +++ b/lib/api/LogManagerImpl.cpp @@ -289,13 +289,7 @@ namespace MAT_NS_BEGIN if (m_httpClient == nullptr) { m_httpClient = HttpClientFactory::Create(); -#ifdef HAVE_MAT_WININET_HTTP_CLIENT - HttpClient_WinInet* client = static_cast(m_httpClient.get()); - if (client != nullptr) - { - client->SetMsRootCheck(m_logConfiguration[CFG_MAP_HTTP][CFG_BOOL_HTTP_MS_ROOT_CHECK]); - } -#endif + m_httpClient->ApplySettings(m_logConfiguration); } else { @@ -366,14 +360,10 @@ namespace MAT_NS_BEGIN /// void LogManagerImpl::Configure() { - // TODO: [maxgolov] - add other config params. -#ifdef HAVE_MAT_WININET_HTTP_CLIENT - HttpClient_WinInet* client = static_cast(m_httpClient.get()); - if (client != nullptr) + if (m_httpClient != nullptr) { - client->SetMsRootCheck(m_logConfiguration[CFG_MAP_HTTP][CFG_BOOL_HTTP_MS_ROOT_CHECK]); + m_httpClient->ApplySettings(m_logConfiguration); } -#endif } LogManagerImpl::~LogManagerImpl() noexcept diff --git a/lib/config/RuntimeConfig_Default.hpp b/lib/config/RuntimeConfig_Default.hpp index 27a8f6758..504aeefe3 100644 --- a/lib/config/RuntimeConfig_Default.hpp +++ b/lib/config/RuntimeConfig_Default.hpp @@ -33,6 +33,9 @@ namespace MAT_NS_BEGIN {/* Parameter that allows to split stats events by tenant */ {"split", false}, {"interval", 1800}, + /* Stats are disabled by default for the built-in shared token + to reduce OneCollector load (see #1420). Set to true to opt in. */ + {"enabled", false}, {"tokenProd", STATS_TOKEN_PROD}, {"tokenInt", STATS_TOKEN_INT}}}, {"utc", @@ -57,7 +60,11 @@ namespace MAT_NS_BEGIN , {"contentEncoding", "deflate"}, /* Optional parameter to require Microsoft Root CA */ - {CFG_BOOL_HTTP_MS_ROOT_CHECK, false}}}, + {CFG_BOOL_HTTP_MS_ROOT_CHECK, false}, + /* Optional parameter for SSL certificate verification (curl) */ + {CFG_BOOL_HTTP_SSL_VERIFY, true}, + /* Optional CA bundle path for OpenSSL-backed curl */ + {CFG_STR_HTTP_SSL_CAINFO, ""}}}, {CFG_MAP_TPM, { {CFG_INT_TPM_MAX_BLOB_BYTES, 2097152}, diff --git a/lib/http/HttpClient_Curl.cpp b/lib/http/HttpClient_Curl.cpp index 18ddabce9..b910cdf28 100644 --- a/lib/http/HttpClient_Curl.cpp +++ b/lib/http/HttpClient_Curl.cpp @@ -14,6 +14,7 @@ #include "utils/Utils.hpp" #include "HttpClient_Curl.hpp" +#include "ILogConfiguration.hpp" namespace MAT_NS_BEGIN { @@ -74,7 +75,13 @@ namespace MAT_NS_BEGIN { requestHeaders[header.first] = header.second; } - auto curlOperation = std::make_shared(curlRequest->m_method, curlRequest->m_url, callback, requestHeaders, curlRequest->m_body); + std::string sslCaInfo; + { + std::lock_guard lock(m_requestsMtx); + sslCaInfo = m_sslCaInfo; + } + + auto curlOperation = std::make_shared(curlRequest->m_method, curlRequest->m_url, callback, requestHeaders, curlRequest->m_body, false, HTTP_CONN_TIMEOUT, m_sslVerify, sslCaInfo); curlRequest->SetOperation(curlOperation); // The lifetime of curlOperation is guarnteed by the call to result.wait() in the d'tor. @@ -125,6 +132,20 @@ namespace MAT_NS_BEGIN { } } + void HttpClient_Curl::ApplySettings(ILogConfiguration& config) + { + SetSslVerification( + config[CFG_MAP_HTTP][CFG_BOOL_HTTP_SSL_VERIFY], + (const char *)config[CFG_MAP_HTTP][CFG_STR_HTTP_SSL_CAINFO]); + } + + void HttpClient_Curl::SetSslVerification(bool sslVerify, const std::string& caInfo) + { + m_sslVerify = sslVerify; + std::lock_guard lock(m_requestsMtx); + m_sslCaInfo = caInfo; + } + void HttpClient_Curl::EraseRequest(std::string const& id) { std::lock_guard lock(m_requestsMtx); diff --git a/lib/http/HttpClient_Curl.hpp b/lib/http/HttpClient_Curl.hpp index cb89ec7e6..c7a5bdecb 100644 --- a/lib/http/HttpClient_Curl.hpp +++ b/lib/http/HttpClient_Curl.hpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -55,12 +56,17 @@ class HttpClient_Curl : public IHttpClient { virtual void SendRequestAsync(IHttpRequest* request, IHttpResponseCallback* callback) override; virtual void CancelRequestAsync(std::string const& id) override; + virtual void ApplySettings(ILogConfiguration& config) override; + void SetSslVerification(bool sslVerify, const std::string& caInfo = ""); + private: void EraseRequest(std::string const& id); void AddRequest(IHttpRequest* request); std::mutex m_requestsMtx; std::map m_requests; + std::atomic m_sslVerify { true }; + std::string m_sslCaInfo; }; class CurlHttpOperation { @@ -91,7 +97,10 @@ class CurlHttpOperation { const std::vector& requestBody = std::vector(), // Default connectivity and response size options bool rawResponse = false, - size_t httpConnTimeout = HTTP_CONN_TIMEOUT) : + size_t httpConnTimeout = HTTP_CONN_TIMEOUT, + // SSL certificate verification options + bool sslVerify = true, + const std::string& sslCaInfo = "") : // Optional connection params rawResponse(rawResponse), @@ -100,6 +109,7 @@ class CurlHttpOperation { m_callback(callback), m_method(method), m_url(url), + m_sslCaInfo(sslCaInfo), // Local vars requestHeaders(requestHeaders), @@ -129,9 +139,11 @@ class CurlHttpOperation { // Specify target URL curl_easy_setopt(curl, CURLOPT_URL, m_url.c_str()); - // TODO: expose SSL cert verification opts via ILogConfiguration - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); // 1L - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); // 2L + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, sslVerify ? 1L : 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, sslVerify ? 2L : 0L); + if (!m_sslCaInfo.empty()) { + curl_easy_setopt(curl, CURLOPT_CAINFO, m_sslCaInfo.c_str()); + } // HTTP/2 please, fallback to HTTP/1.1 if not supported curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); @@ -423,6 +435,7 @@ class CurlHttpOperation { // Request values std::string m_method; std::string m_url; + std::string m_sslCaInfo; const std::map& requestHeaders; const std::vector& requestBody; struct curl_slist *m_headersChunk = nullptr; @@ -452,28 +465,12 @@ class CurlHttpOperation { */ static int WaitOnSocket(curl_socket_t sockfd, int for_recv, long timeout_ms) { - struct timeval tv; - fd_set infd, outfd, errfd; - int res; - - tv.tv_sec = timeout_ms / 1000; - tv.tv_usec = (timeout_ms % 1000) * 1000; - - FD_ZERO(&infd); - FD_ZERO(&outfd); - FD_ZERO(&errfd); - - FD_SET(sockfd, &errfd); /* always check for error */ - - if(for_recv) { - FD_SET(sockfd, &infd); - } else { - FD_SET(sockfd, &outfd); - } - - /* select() returns the number of signalled sockets or -1 */ - res = select((int)sockfd + 1, &infd, &outfd, &errfd, &tv); - return res; + struct pollfd pfd; + pfd.fd = sockfd; + pfd.events = for_recv ? POLLIN : POLLOUT; + // Cap timeout to max int value to avoid overflow in poll() + auto timeout = std::min(timeout_ms, static_cast(std::numeric_limits::max())); + return poll(&pfd, 1, static_cast(timeout)); } // Raw response buffer @@ -539,4 +536,3 @@ class CurlHttpOperation { #endif // HAVE_MAT_DEFAULT_HTTP_CLIENT #endif // HTTPCLIENTCURL_HPP - diff --git a/lib/http/HttpClient_WinInet.cpp b/lib/http/HttpClient_WinInet.cpp index 637b10778..eaefb2318 100644 --- a/lib/http/HttpClient_WinInet.cpp +++ b/lib/http/HttpClient_WinInet.cpp @@ -546,6 +546,11 @@ void HttpClient_WinInet::CancelAllRequests() /// Enforces MS-root server certificate check. /// /// if set to true [enforce verification that server cert is MS-Rooted]. +void HttpClient_WinInet::ApplySettings(ILogConfiguration& config) +{ + SetMsRootCheck(config[CFG_MAP_HTTP][CFG_BOOL_HTTP_MS_ROOT_CHECK]); +} + void HttpClient_WinInet::SetMsRootCheck(bool enforceMsRoot) { m_msRootCheck = enforceMsRoot; diff --git a/lib/http/HttpClient_WinInet.hpp b/lib/http/HttpClient_WinInet.hpp index e5936fcc3..7e9379ded 100644 --- a/lib/http/HttpClient_WinInet.hpp +++ b/lib/http/HttpClient_WinInet.hpp @@ -30,6 +30,8 @@ class HttpClient_WinInet : public IHttpClient { virtual void CancelRequestAsync(std::string const& id) final; virtual void CancelAllRequests() final; + virtual void ApplySettings(ILogConfiguration& config) override; + // Methods unique to WinInet implementation. void SetMsRootCheck(bool enforceMsRoot); bool IsMsRootCheckRequired(); diff --git a/lib/include/public/IHttpClient.hpp b/lib/include/public/IHttpClient.hpp index e9a71a210..89e5e6cf0 100644 --- a/lib/include/public/IHttpClient.hpp +++ b/lib/include/public/IHttpClient.hpp @@ -18,6 +18,7 @@ ///@cond INTERNAL_DOCS namespace MAT_NS_BEGIN { + class ILogConfiguration; /// /// The HttpHeaders class contains a set of HTTP headers. /// @@ -543,6 +544,14 @@ namespace MAT_NS_BEGIN virtual void CancelRequestAsync(std::string const& id) = 0; virtual void CancelAllRequests() {} + + /// + /// Apply HTTP settings from the log configuration. + /// Subclasses override to handle platform-specific options. + /// Default implementation is a no-op. + /// + /// The log configuration to read settings from. + virtual void ApplySettings(ILogConfiguration& /*config*/) {} }; /// @endcond diff --git a/lib/include/public/ILogConfiguration.hpp b/lib/include/public/ILogConfiguration.hpp index 952bc2651..1cb8103b8 100644 --- a/lib/include/public/ILogConfiguration.hpp +++ b/lib/include/public/ILogConfiguration.hpp @@ -361,6 +361,16 @@ namespace MAT_NS_BEGIN /// static constexpr const char* const CFG_BOOL_HTTP_COMPRESSION = "compress"; + /// + /// HTTP configuration: SSL certificate verification (peer + host) + /// + static constexpr const char* const CFG_BOOL_HTTP_SSL_VERIFY = "sslVerify"; + + /// + /// HTTP configuration: SSL CA bundle file path (for libcurl/OpenSSL) + /// + static constexpr const char* const CFG_STR_HTTP_SSL_CAINFO = "sslCaInfo"; + /// /// TPM configuration map /// diff --git a/lib/include/public/Version.hpp b/lib/include/public/Version.hpp index ce9e8fd79..850519486 100644 --- a/lib/include/public/Version.hpp +++ b/lib/include/public/Version.hpp @@ -6,8 +6,8 @@ #define MAT_VERSION_HPP // WARNING: DO NOT MODIFY THIS FILE! // This file has been automatically generated, manual changes will be lost. -#define BUILD_VERSION_STR "3.10.40.1" -#define BUILD_VERSION 3,10,40,1 +#define BUILD_VERSION_STR "3.10.100.1" +#define BUILD_VERSION 3,10,100,1 #ifndef RESOURCE_COMPILER_INVOKED #include "ctmacros.hpp" @@ -18,7 +18,7 @@ namespace MAT_NS_BEGIN { uint64_t const Version = ((uint64_t)3 << 48) | ((uint64_t)10 << 32) | - ((uint64_t)40 << 16) | + ((uint64_t)100 << 16) | ((uint64_t)1); } MAT_NS_END diff --git a/lib/offline/OfflineStorage_Room.cpp b/lib/offline/OfflineStorage_Room.cpp index ab7d43264..423aecde3 100644 --- a/lib/offline/OfflineStorage_Room.cpp +++ b/lib/offline/OfflineStorage_Room.cpp @@ -11,6 +11,19 @@ namespace { static constexpr bool s_throwExceptions = true; + + // RAII guard that deletes a JNI global class reference on all exit paths, + // including std::logic_error (ThrowLogic) and std::runtime_error (ThrowRuntime). + struct GlobalRefGuard { + JNIEnv* jni; + jclass* ref_ptr; + ~GlobalRefGuard() noexcept { + if (ref_ptr && *ref_ptr) { + jni->DeleteGlobalRef(*ref_ptr); + *ref_ptr = nullptr; + } + } + }; } namespace MAT_NS_BEGIN @@ -387,17 +400,23 @@ namespace MAT_NS_BEGIN { break; // out of r > c loop; no more records } - // we don't collect these here because GetObjectClass is - // less fragile than FindClass - jclass record_class = nullptr; - jfieldID id_id; - jfieldID tenantToken_id; - jfieldID latency_id; - jfieldID persistence_id; - jfieldID timestamp_id; - jfieldID retryCount_id; - jfieldID reservedUntil_id; - jfieldID blob_id; + // Field IDs are looked up once from the first record's class and reused. + // record_class is stored as a global reference so it remains valid across + // pushLocalFrame/popLocalFrame boundaries (local refs are freed on popLocalFrame, + // causing a JNI abort on ART if reused in subsequent iterations). + jclass record_class = nullptr; + jfieldID id_id = nullptr; + jfieldID tenantToken_id = nullptr; + jfieldID latency_id = nullptr; + jfieldID persistence_id = nullptr; + jfieldID timestamp_id = nullptr; + jfieldID retryCount_id = nullptr; + jfieldID reservedUntil_id = nullptr; + jfieldID blob_id = nullptr; + // RAII guard: deletes record_class global ref on all exit paths, + // including std::logic_error (ThrowLogic) and std::runtime_error + // (ThrowRuntime) which the catch block below would not otherwise clean up. + GlobalRefGuard record_class_guard{env.getInner(), &record_class}; // Set limits for conversion from int to enum int latency_lb = static_cast(EventLatency_Off); @@ -412,7 +431,14 @@ namespace MAT_NS_BEGIN ThrowLogic(env, "getAndReserve element"); if (!record_class) { - record_class = env->GetObjectClass(record); + // Promote to a global ref so it survives popLocalFrame on + // subsequent iterations. Freed by record_class_guard on exit. + jclass local_class = env->GetObjectClass(record); + record_class = static_cast(env->NewGlobalRef(local_class)); + if (!record_class) + { + MATSDK_THROW(std::runtime_error("NewGlobalRef failed")); + } id_id = env->GetFieldID(record_class, "id", "J"); ThrowLogic(env, "gar id"); tenantToken_id = env->GetFieldID(record_class, "tenantToken", @@ -663,9 +689,12 @@ namespace MAT_NS_BEGIN if (tokens > 0) { DroppedMap dropped; - jclass bt_class = nullptr; - jfieldID token_id; - jfieldID count_id; + // bt_class stored as a global ref to survive popLocalFrame across iterations. + jclass bt_class = nullptr; + jfieldID token_id = nullptr; + jfieldID count_id = nullptr; + // RAII guard: frees bt_class on all exit paths including exceptions. + GlobalRefGuard bt_class_guard{env.getInner(), &bt_class}; for (size_t index = 0; index < tokens; ++index) { env.pushLocalFrame(8); @@ -673,7 +702,14 @@ namespace MAT_NS_BEGIN ThrowRuntime(env, "Exception fetching element from results"); if (!bt_class) { - bt_class = env->GetObjectClass(byTenant); + // Promote to a global ref so it survives popLocalFrame. + // Freed by bt_class_guard on exit. + jclass local_class = env->GetObjectClass(byTenant); + bt_class = static_cast(env->NewGlobalRef(local_class)); + if (!bt_class) + { + MATSDK_THROW(std::runtime_error("NewGlobalRef failed")); + } token_id = env->GetFieldID(bt_class, "tenantToken", "Ljava/lang/String;"); ThrowLogic(env, "Error fetching tenantToken field id"); @@ -1160,15 +1196,18 @@ namespace MAT_NS_BEGIN "(ZIJ)[Lcom/microsoft/applications/events/StorageRecord;"); ThrowLogic(env, "getRecords method"); - jclass record_class = nullptr; - jfieldID id_id = nullptr; - jfieldID tenantToken_id; - jfieldID latency_id; - jfieldID persistence_id; - jfieldID timestamp_id; - jfieldID retryCount_id; - jfieldID reservedUntil_id; - jfieldID blob_id; + // record_class stored as a global ref to survive popLocalFrame across iterations. + jclass record_class = nullptr; + jfieldID id_id = nullptr; + jfieldID tenantToken_id = nullptr; + jfieldID latency_id = nullptr; + jfieldID persistence_id = nullptr; + jfieldID timestamp_id = nullptr; + jfieldID retryCount_id = nullptr; + jfieldID reservedUntil_id = nullptr; + jfieldID blob_id = nullptr; + // RAII guard: frees record_class on all exit paths including exceptions. + GlobalRefGuard record_class_guard{env.getInner(), &record_class}; auto java_records = static_cast(env->CallObjectMethod(m_room, method, @@ -1185,7 +1224,14 @@ namespace MAT_NS_BEGIN ThrowLogic(env, "access result element"); if (!record_class) { - record_class = env->GetObjectClass(record); + // Promote to a global ref so it survives popLocalFrame. + // Freed by record_class_guard on exit. + jclass local_class = env->GetObjectClass(record); + record_class = static_cast(env->NewGlobalRef(local_class)); + if (!record_class) + { + MATSDK_THROW(std::runtime_error("NewGlobalRef failed")); + } id_id = env->GetFieldID(record_class, "id", "J"); ThrowLogic(env, "id field"); tenantToken_id = env->GetFieldID(record_class, "tenantToken", diff --git a/lib/shared/Shared.vcxitems b/lib/shared/Shared.vcxitems index bf3d5df64..4b7c095ff 100644 --- a/lib/shared/Shared.vcxitems +++ b/lib/shared/Shared.vcxitems @@ -24,7 +24,6 @@ - diff --git a/lib/shared/Shared.vcxitems.filters b/lib/shared/Shared.vcxitems.filters index c488b869b..1868316bf 100644 --- a/lib/shared/Shared.vcxitems.filters +++ b/lib/shared/Shared.vcxitems.filters @@ -1,7 +1,6 @@  - diff --git a/lib/stats/Statistics.cpp b/lib/stats/Statistics.cpp index 2f421714c..4abf09563 100644 --- a/lib/stats/Statistics.cpp +++ b/lib/stats/Statistics.cpp @@ -7,6 +7,7 @@ #include "Statistics.hpp" #include "ILogManager.hpp" +#include "mat/config.h" #include "utils/Utils.hpp" #include @@ -60,12 +61,21 @@ namespace MAT_NS_BEGIN { return; } + std::string tenantToken = m_config.GetMetaStatsTenantToken(); + // Stats are disabled by default for the built-in shared token to + // reduce OneCollector load (see #1420). Custom tokens always send. + // Set config["metaStats"]["enabled"] = true to opt in. + bool isDefaultToken = (tenantToken == STATS_TOKEN_PROD || tenantToken == STATS_TOKEN_INT); + if (isDefaultToken && !static_cast(m_config[CFG_MAP_METASTATS_CONFIG]["enabled"])) + { + return; + } + std::vector< ::CsProtocol::Record> records; { LOCKGUARD(m_metaStats_mtx); records = m_metaStats.generateStatsEvent(rollupKind); } - std::string tenantToken = m_config.GetMetaStatsTenantToken(); for (auto& record : records) { diff --git a/tests/functests/AISendTests.cpp b/tests/functests/AISendTests.cpp index 180e9b074..dfd0bf185 100644 --- a/tests/functests/AISendTests.cpp +++ b/tests/functests/AISendTests.cpp @@ -118,7 +118,7 @@ class AISendTests : public ::testing::Test, } int port = server.addListeningPort(HTTP_PORT); std::ostringstream os; - os << "localhost:" << port; + os << "127.0.0.1:" << port; serverAddress = "http://" + os.str() + "/v2/track"; server.setServerName(os.str()); server.addHandler("/v2/track", *this); @@ -142,6 +142,9 @@ class AISendTests : public ::testing::Test, fileName += PATH_SEPARATOR_CHAR; fileName += TEST_STORAGE_FILENAME; std::remove(fileName.c_str()); + std::remove((fileName + "-wal").c_str()); + std::remove((fileName + "-shm").c_str()); + std::remove((fileName + "-journal").c_str()); } virtual void Initialize(DebugEventListener& debugListener, std::string const& path, bool compression) diff --git a/tests/functests/APITest.cpp b/tests/functests/APITest.cpp index 11524cd5a..0347807f6 100644 --- a/tests/functests/APITest.cpp +++ b/tests/functests/APITest.cpp @@ -302,7 +302,11 @@ static std::string GetStoragePath() static void CleanStorage() { - std::remove(GetStoragePath().c_str()); + std::string path = GetStoragePath(); + std::remove(path.c_str()); + std::remove((path + "-wal").c_str()); + std::remove((path + "-shm").c_str()); + std::remove((path + "-journal").c_str()); } #if 0 @@ -391,6 +395,10 @@ TEST(APITest, LogManager_Initialize_DebugEventListener) LogManager::GetLogger()->LogEvent(eventToLog); } LogManager::Flush(); + // Storage-full callback fires asynchronously; give it time to arrive + for (int i = 0; i < 50 && debugListener.storageFullPct.load() < 100; i++) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } EXPECT_GE(debugListener.storageFullPct.load(), (unsigned)100); LogManager::FlushAndTeardown(); @@ -404,8 +412,20 @@ TEST(APITest, LogManager_Initialize_DebugEventListener) debugListener.numSent = 0; debugListener.numLogged = 0; - CleanStorage(); + // Use a unique DB path for Phase 2/3 on every invocation. The SDK + // closes SQLite with sqlite3_close_v2(), which defers file-descriptor + // cleanup when prepared statements linger. If we reuse a fixed path + // and std::remove() the old files while deferred fds are still open, + // iOS emits "vnode unlinked while in use" and may invalidate the new + // DB's descriptors. A fresh, never-before-seen path sidesteps the + // problem entirely — no stale files, no collisions, no sleep needed. + static std::atomic s_phase2Counter{0}; + std::string phase2Path = GetStoragePath() + ".phase2." + + std::to_string(s_phase2Counter.fetch_add(1)); + configuration[CFG_STR_CACHE_FILE_PATH] = phase2Path; + configuration[CFG_INT_CACHE_FILE_SIZE] = 0; // No size limit for phase 2 ILogger *result = LogManager::Initialize(TEST_TOKEN, configuration); + LogManager::PauseTransmission(); // Pause before logging to avoid production uploads // Log some foo size_t numIterations = MAX_ITERATIONS; @@ -416,10 +436,6 @@ TEST(APITest, LogManager_Initialize_DebugEventListener) EXPECT_EQ(0u, debugListener.numDropped); EXPECT_EQ(0u, debugListener.numReject); - LogManager::UploadNow(); // Try to upload whatever we got - PAL::sleep(10000); // Give enough time to upload at least one event - EXPECT_NE(0u, debugListener.numSent); // Some posts must succeed within 500ms - LogManager::PauseTransmission(); // There could still be some pending at this point LogManager::Flush(); // Save all pending to disk numIterations = MAX_ITERATIONS; @@ -434,15 +450,27 @@ TEST(APITest, LogManager_Initialize_DebugEventListener) LogManager::Flush(); EXPECT_EQ(MAX_ITERATIONS, debugListener.numCached); + // Phase 3: resume transmission and upload the cached events LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::ResumeTransmission(); + LogManager::UploadNow(); + PAL::sleep(10000); // Give enough time to upload LogManager::FlushAndTeardown(); // Check that we sent all of logged + whatever left overs // prior to PauseTransmission EXPECT_GE(debugListener.numSent, debugListener.numLogged); + debugListener.printStats(); removeAllListeners(debugListener); + + // Best-effort cleanup. Assertions have already passed, so if + // sqlite3_close_v2 deferred cleanup triggers a vnode warning here + // it is harmless. + std::remove(phase2Path.c_str()); + std::remove((phase2Path + "-wal").c_str()); + std::remove((phase2Path + "-shm").c_str()); + std::remove((phase2Path + "-journal").c_str()); } #ifdef _WIN32 @@ -1180,10 +1208,12 @@ TEST(APITest, LogManager_BadNetwork_Test) // Clean temp file first const char *cacheFilePath = "bad-network.db"; std::string fileName = MAT::GetTempDirectory(); - fileName += "\\"; fileName += cacheFilePath; printf("remove %s\n", fileName.c_str()); std::remove(fileName.c_str()); + std::remove((fileName + "-wal").c_str()); + std::remove((fileName + "-shm").c_str()); + std::remove((fileName + "-journal").c_str()); for (auto url : { #if 0 /* [MG}: Temporary change to avoid GitHub Actions crash #92 */ diff --git a/tests/functests/BasicFuncTests.cpp b/tests/functests/BasicFuncTests.cpp index 51848d054..438411425 100644 --- a/tests/functests/BasicFuncTests.cpp +++ b/tests/functests/BasicFuncTests.cpp @@ -154,7 +154,7 @@ class BasicFuncTests : public ::testing::Test, } int port = server.addListeningPort(HTTP_PORT); std::ostringstream os; - os << "localhost:" << port; + os << "127.0.0.1:" << port; serverAddress = "http://" + os.str() + "/simple/"; server.setServerName(os.str()); server.addHandler("/simple/", *this); @@ -179,6 +179,11 @@ class BasicFuncTests : public ::testing::Test, fileName += PATH_SEPARATOR_CHAR; fileName += TEST_STORAGE_FILENAME; std::remove(fileName.c_str()); + // SQLite WAL mode creates companion journal files that must also + // be removed to avoid "vnode unlinked while in use" on iOS. + std::remove((fileName + "-wal").c_str()); + std::remove((fileName + "-shm").c_str()); + std::remove((fileName + "-journal").c_str()); } virtual void Initialize() @@ -196,16 +201,22 @@ class BasicFuncTests : public ::testing::Test, configuration[CFG_INT_RAM_QUEUE_SIZE] = 4096 * 20; configuration[CFG_STR_CACHE_FILE_PATH] = TEST_STORAGE_FILENAME; + configuration[CFG_INT_CACHE_FILE_SIZE] = 4096 * 1024; // 4MB default configuration[CFG_INT_MAX_TEARDOWN_TIME] = 2; // 2 seconds wait on shutdown + configuration[CFG_INT_STORAGE_FULL_PCT] = 75; // default + configuration[CFG_INT_STORAGE_FULL_CHECK_TIME] = 5000; // default 5s configuration[CFG_STR_COLLECTOR_URL] = serverAddress.c_str(); configuration[CFG_MAP_HTTP][CFG_BOOL_HTTP_COMPRESSION] = false; // disable compression for now + configuration[CFG_MAP_TPM][CFG_STR_TPM_BACKOFF] = "E,500,5000,2,1"; // faster retry for localhost tests configuration[CFG_MAP_METASTATS_CONFIG][CFG_INT_METASTATS_INTERVAL] = 30 * 60; // 30 mins + configuration[CFG_MAP_METASTATS_CONFIG]["enabled"] = true; // opt in to stats (disabled by default since #1420) configuration["name"] = __FILE__; configuration["version"] = "1.0.0"; configuration["config"] = { { "host", __FILE__ } }; // Host instance LogManager::Initialize(TEST_TOKEN, configuration); + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::SetLevelFilter(DIAG_LEVEL_DEFAULT, { DIAG_LEVEL_DEFAULT_MIN, DIAG_LEVEL_DEFAULT_MAX }); LogManager::ResumeTransmission(); @@ -257,15 +268,16 @@ class BasicFuncTests : public ::testing::Test, size_t lastIdx = 0; while ( ((PAL::getUtcSystemTimeMs()-start)<(1000* timeOutSec)) && (receivedEvents!=expected_count) ) { - /* Give time for our friendly HTTP server thread to process incoming request */ - std::this_thread::yield(); + /* Give time for HTTP server thread to process incoming request. + * sleep(10) instead of yield() reduces CPU contention on single-core + * iOS simulator runners and gives the network stack time to deliver. */ + PAL::sleep(10); { LOCKGUARD(mtx_requests); if (receivedRequests.size()) { size_t size = receivedRequests.size(); - //requests can come within 100 milisec sleep for (size_t index = lastIdx; index < size; index++) { auto request = receivedRequests.at(index); @@ -573,6 +585,7 @@ TEST_F(BasicFuncTests, sendNoPriorityEvents) event2.SetProperty("property2", "another value"); logger->LogEvent(event2); + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::UploadNow(); waitForEvents(1, 3); EXPECT_GE(receivedRequests.size(), (size_t)1); @@ -671,6 +684,7 @@ TEST_F(BasicFuncTests, sendDifferentPriorityEvents) logger->LogEvent(event2); + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::UploadNow(); // 2 x customer events + 1 x evt_stats on start waitForEvents(1, 3); @@ -718,6 +732,7 @@ TEST_F(BasicFuncTests, sendMultipleTenantsTogether) logger2->LogEvent(event2); + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::UploadNow(); // 2 x customer events + 1 x evt_stats on start @@ -748,6 +763,7 @@ TEST_F(BasicFuncTests, configDecorations) EventProperties event4("4th_event"); logger->LogEvent(event4); + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::UploadNow(); waitForEvents(2, 5); @@ -785,10 +801,11 @@ TEST_F(BasicFuncTests, restartRecoversEventsFromStorage) fooEvent.SetLatency(EventLatency_RealTime); fooEvent.SetPersistence(EventPersistence_Critical); LogManager::GetLogger()->LogEvent(fooEvent); + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::UploadNow(); // 1st request for realtime event - waitForEvents(3, 5); // start, first_event, second_event, ongoing, stop, start, fooEvent + waitForEvents(10, 5); // start, first_event, second_event, ongoing, stop, start, fooEvent // we drop two of the events during pause, though. EXPECT_GE(receivedRequests.size(), (size_t)1); if (receivedRequests.size() != 0) @@ -852,7 +869,7 @@ TEST_F(BasicFuncTests, storageFileSizeDoesntExceedConfiguredSize) { Initialize(); - waitForEvents(2, 8); + waitForEvents(5, 8); if (receivedRequests.size()) { auto payload = decodeRequest(receivedRequests[0], false); @@ -897,8 +914,9 @@ TEST_F(BasicFuncTests, sendMetaStatsOnStart) // Check Initialize(); LogManager::ResumeTransmission(); // ? + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::UploadNow(); - PAL::sleep(2000); + waitForEvents(5, 4); // (start + stop) + (2 events + start) auto r2 = records(); ASSERT_GE(r2.size(), (size_t)4); // (start + stop) + (2 events + start) @@ -927,8 +945,9 @@ TEST_F(BasicFuncTests, DiagLevelRequiredOnly_OneEventWithoutLevelOneWithButNotAl eventWithAllowedLevel.SetLevel(DIAG_LEVEL_REQUIRED); logger->LogEvent(eventWithAllowedLevel); + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::UploadNow(); - waitForEvents(1 /*timeout*/, 2 /*expected count*/); // Start and EventWithAllowedLevel + waitForEvents(5 /*timeout*/, 2 /*expected count*/); // Start and EventWithAllowedLevel ASSERT_EQ(records().size(), static_cast(2)); // Start and EventWithAllowedLevel @@ -970,8 +989,9 @@ TEST_F(BasicFuncTests, DiagLevelRequiredOnly_SendTwoEventsUpdateAllowedLevelsSen LogManager::SetLevelFilter(DIAG_LEVEL_OPTIONAL, { DIAG_LEVEL_OPTIONAL, DIAG_LEVEL_REQUIRED }); SendEventWithOptionalThenRequired(logger); + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::UploadNow(); - waitForEvents(2 /*timeout*/, 4 /*expected count*/); // Start and EventWithAllowedLevel + waitForEvents(5 /*timeout*/, 4 /*expected count*/); // Start and EventWithAllowedLevel auto sentRecords = records(); ASSERT_EQ(sentRecords.size(), static_cast(4)); // Start and EventWithAllowedLevel @@ -1140,6 +1160,7 @@ TEST_F(BasicFuncTests, killSwitchWorks) configuration[CFG_STR_COLLECTOR_URL] = serverAddress.c_str(); configuration[CFG_MAP_HTTP][CFG_BOOL_HTTP_COMPRESSION] = false; // disable compression for now configuration[CFG_MAP_METASTATS_CONFIG]["interval"] = 30 * 60; // 30 mins + configuration[CFG_MAP_METASTATS_CONFIG]["enabled"] = true; // opt in to stats (disabled by default since #1420) configuration["name"] = __FILE__; configuration["version"] = "1.0.0"; @@ -1173,7 +1194,8 @@ TEST_F(BasicFuncTests, killSwitchWorks) myLogger->LogEvent(event2); } } - // Try to upload and wait for 2 seconds to complete + // Try to upload and wait for completion + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::UploadNow(); PAL::sleep(2000); @@ -1222,6 +1244,7 @@ TEST_F(BasicFuncTests, killIsTemporary) configuration[CFG_STR_COLLECTOR_URL] = serverAddress.c_str(); configuration[CFG_MAP_HTTP][CFG_BOOL_HTTP_COMPRESSION] = false; // disable compression for now configuration[CFG_MAP_METASTATS_CONFIG]["interval"] = 30 * 60; // 30 mins + configuration[CFG_MAP_METASTATS_CONFIG]["enabled"] = true; // opt in to stats (disabled by default since #1420) configuration["name"] = __FILE__; configuration["version"] = "1.0.0"; diff --git a/tests/functests/CMakeLists.txt b/tests/functests/CMakeLists.txt index 656f8f866..cb295e5c8 100644 --- a/tests/functests/CMakeLists.txt +++ b/tests/functests/CMakeLists.txt @@ -1,4 +1,4 @@ -message("--- functests") +message(STATUS "Building functests") set(SRCS APITest.cpp @@ -8,47 +8,40 @@ set(SRCS MultipleLogManagersTests.cpp ) -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/ AND BUILD_PRIVACYGUARD) +if(EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/privacyguard/" AND BUILD_PRIVACYGUARD) add_definitions(-DHAVE_MAT_PRIVACYGUARD) list(APPEND SRCS - PrivacyGuardFuncTests.cpp + ${CMAKE_SOURCE_DIR}/lib/modules/privacyguard/tests/functests/PrivacyGuardFuncTests.cpp ) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/ AND BUILD_SANITIZER) +if(EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/sanitizer/" AND BUILD_SANITIZER) list(APPEND SRCS - SanitizerFuncTests.cpp + ${CMAKE_SOURCE_DIR}/lib/modules/sanitizer/tests/functests/SanitizerFuncTests.cpp ) endif() -if(EXISTS ${CMAKE_SOURCE_DIR}/lib/modules/dataviewer/) +if(EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/dataviewer/") list(APPEND SRCS ${CMAKE_SOURCE_DIR}/lib/modules/dataviewer/tests/functests/DefaultDataViewerFuncTests.cpp ) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector/ AND BUILD_LIVEEVENTINSPECTOR) +if(EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/liveeventinspector/" AND BUILD_LIVEEVENTINSPECTOR) add_definitions(-DHAVE_MAT_LIVEEVENTINSPECTOR) list(APPEND SRCS - LiveEventInspectorFuncTests.cpp + ${CMAKE_SOURCE_DIR}/lib/modules/liveeventinspector/tests/functests/LiveEventInspectorFuncTests.cpp ) endif() -if (EXISTS ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests) +if (EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/exp/tests") list(APPEND SRCS ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests/functests/ECSClientFuncTests.cpp ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests/functests/ECSClientRealworldFuncTests.cpp ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests/functests/ECSConfigCacheFuncTests.cpp ) - if (EXISTS ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests/functests/test.json) - if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.21") - # Use file(COPY_FILE ...) for CMake 3.21 and later - file(COPY_FILE ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests/functests/test.json ${CMAKE_BINARY_DIR}/test.json) - else() - # Use file(COPY ...) as an alternative for older versions - file(COPY ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests/functests/test.json - DESTINATION ${CMAKE_BINARY_DIR}) - endif() + if (EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/exp/tests/functests/test.json") + configure_file(${CMAKE_SOURCE_DIR}/lib/modules/exp/tests/functests/test.json ${CMAKE_BINARY_DIR}/test.json COPYONLY) endif() endif() @@ -63,11 +56,11 @@ endif() if(PAL_IMPLEMENTATION STREQUAL "WIN32") # Link against prebuilt libraries on Windows - message("--- WIN32: Linking against prebuilt libraries") - message("--- WIN32: ... ${CMAKE_BINARY_DIR}/gtest") - message("--- WIN32: ... ${CMAKE_BINARY_DIR}/gmock") - message("--- WIN32: ... ${CMAKE_BINARY_DIR}/zlib") - message("--- WIN32: ... ${CMAKE_BINARY_DIR}/sqlite") + message(STATUS "WIN32: Linking against prebuilt libraries") + message(STATUS "WIN32: ... ${CMAKE_BINARY_DIR}/gtest") + message(STATUS "WIN32: ... ${CMAKE_BINARY_DIR}/gmock") + message(STATUS "WIN32: ... ${CMAKE_BINARY_DIR}/zlib") + message(STATUS "WIN32: ... ${CMAKE_BINARY_DIR}/sqlite") # link_directories(${CMAKE_BINARY_DIR}/gtest/ ${CMAKE_BINARY_DIR}/gmock/ ${CMAKE_BINARY_DIR}/zlib/ ${CMAKE_BINARY_DIR}/sqlite/) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../zlib ) target_link_libraries(FuncTests @@ -109,10 +102,9 @@ else() set (PLATFORM_LIBS "atomic") endif() - # Find libraries - message("--- Linking libraries! ") - message("Current Dir: ${CMAKE_CURRENT_SOURCE_DIR}") - message("Binary Dir: ${CMAKE_BINARY_DIR}") + message(STATUS "Linking libraries") + message(STATUS "Current Dir: ${CMAKE_CURRENT_SOURCE_DIR}") + message(STATUS "Binary Dir: ${CMAKE_BINARY_DIR}") set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) diff --git a/tests/functests/FuncTests.vcxproj b/tests/functests/FuncTests.vcxproj index 50f4e2427..87b0b5b53 100644 --- a/tests/functests/FuncTests.vcxproj +++ b/tests/functests/FuncTests.vcxproj @@ -436,7 +436,6 @@ - diff --git a/tests/functests/FuncTests.vcxproj.filters b/tests/functests/FuncTests.vcxproj.filters index 22f2ac5d9..e19381a72 100644 --- a/tests/functests/FuncTests.vcxproj.filters +++ b/tests/functests/FuncTests.vcxproj.filters @@ -53,9 +53,6 @@ mocks - - mocks - mocks diff --git a/tests/functests/MultipleLogManagersTests.cpp b/tests/functests/MultipleLogManagersTests.cpp index 25f99f175..eac2bfd00 100644 --- a/tests/functests/MultipleLogManagersTests.cpp +++ b/tests/functests/MultipleLogManagersTests.cpp @@ -54,7 +54,7 @@ class RequestHandler : public HttpServer::Callback } private: - size_t m_count {}; + std::atomic m_count {}; int m_id ; }; @@ -63,9 +63,9 @@ class MultipleLogManagersTests : public ::testing::Test protected: std::string serverAddress; ILogConfiguration config1, config2, config3; - RequestHandler callback1 = RequestHandler(1); - RequestHandler callback2 = RequestHandler(2); - RequestHandler callback3 = RequestHandler(3); + RequestHandler callback1{1}; + RequestHandler callback2{2}; + RequestHandler callback3{3}; HttpServer server; @@ -74,7 +74,7 @@ class MultipleLogManagersTests : public ::testing::Test { int port = server.addListeningPort(0); std::ostringstream os; - os << "localhost:" << port; + os << "127.0.0.1:" << port; server.setServerName(os.str()); serverAddress = "http://" + os.str(); @@ -196,7 +196,7 @@ TEST_F(MultipleLogManagersTests, ThreeInstancesCoexist) lm2->GetLogController()->UploadNow(); lm3->GetLogController()->UploadNow(); - waitForRequestsMultipleLogManager(10000, 1, 1, 1); + waitForRequestsMultipleLogManager(20000, 1, 1, 1); lm1.reset(); lm2.reset(); @@ -224,7 +224,7 @@ TEST_F(MultipleLogManagersTests, MultiProcessesLogManager) CAPTURE_PERF_STATS("Events Sent"); lm->GetLogController()->UploadNow(); CAPTURE_PERF_STATS("Events Uploaded"); - waitForRequestsSingleLogManager(10000, 2); + waitForRequestsSingleLogManager(20000, 2); lm.reset(); CAPTURE_PERF_STATS("Log Manager deleted"); } diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 891150d34..1b29db3bf 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -1,4 +1,4 @@ -message("--- unittests") +message(STATUS "Building unittests") set(SRCS AIJsonSerializerTests.cpp @@ -19,6 +19,7 @@ set(SRCS EventPropertiesTests.cpp GuidTests.cpp HttpClientCAPITests.cpp + HttpClientCurlTests.cpp HttpClientManagerTests.cpp HttpClientTests.cpp HttpDeflateCompressionTests.cpp @@ -53,7 +54,7 @@ set_source_files_properties(${SRCS} PROPERTIES COMPILE_FLAGS -Wno-deprecated-dec # Enable Azure Monitor unit tests when the module is present. # The AIJsonSerializer test sources are guarded by HAVE_MAT_AI. -if (EXISTS ${CMAKE_SOURCE_DIR}/lib/modules/azmon/AIJsonSerializer.hpp) +if (EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/azmon/AIJsonSerializer.hpp") add_definitions(-DHAVE_MAT_AI) endif() @@ -65,7 +66,7 @@ if (APPLE) endif() endif() -if (EXISTS ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests) +if (EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/exp/tests") list(APPEND SRCS ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests/unittests/ECSConfigCacheTests.cpp ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests/unittests/ECSClientUtilsTests.cpp @@ -73,7 +74,7 @@ if (EXISTS ${CMAKE_SOURCE_DIR}/lib/modules/exp/tests) ) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/ AND BUILD_PRIVACYGUARD) +if(EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/privacyguard/" AND BUILD_PRIVACYGUARD) add_definitions(-DHAVE_MAT_PRIVACYGUARD) list(APPEND SRCS ${CMAKE_SOURCE_DIR}/lib/modules/privacyguard/tests/unittests/InitializationConfigurationTests.cpp @@ -84,7 +85,7 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/ AND BUILD_PRIVACYGUA ) endif() -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/ AND BUILD_SANITIZER) +if(EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/sanitizer/" AND BUILD_SANITIZER) list(APPEND SRCS ${CMAKE_SOURCE_DIR}/lib/modules/sanitizer/tests/unittests/SanitizerJwtTests.cpp ${CMAKE_SOURCE_DIR}/lib/modules/sanitizer/tests/unittests/SanitizerProviderTests.cpp @@ -96,7 +97,7 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/ AND BUILD_SANITIZER) ) endif() -if(EXISTS ${CMAKE_SOURCE_DIR}/lib/modules/dataviewer/) +if(EXISTS "${CMAKE_SOURCE_DIR}/lib/modules/dataviewer/") list(APPEND SRCS ${CMAKE_SOURCE_DIR}/lib/modules/dataviewer/tests/unittests/DefaultDataViewerTests.cpp DataViewerCollectionTests.cpp @@ -114,11 +115,11 @@ endif() if(PAL_IMPLEMENTATION STREQUAL "WIN32") # Link against prebuilt libraries on Windows - message("--- WIN32: Linking against prebuilt libraries") - message("--- WIN32: ... ${CMAKE_BINARY_DIR}/gtest") - message("--- WIN32: ... ${CMAKE_BINARY_DIR}/gmock") - message("--- WIN32: ... ${CMAKE_BINARY_DIR}/zlib") - message("--- WIN32: ... ${CMAKE_BINARY_DIR}/sqlite") + message(STATUS "WIN32: Linking against prebuilt libraries") + message(STATUS "WIN32: ... ${CMAKE_BINARY_DIR}/gtest") + message(STATUS "WIN32: ... ${CMAKE_BINARY_DIR}/gmock") + message(STATUS "WIN32: ... ${CMAKE_BINARY_DIR}/zlib") + message(STATUS "WIN32: ... ${CMAKE_BINARY_DIR}/sqlite") # link_directories(${CMAKE_BINARY_DIR}/gtest/ ${CMAKE_BINARY_DIR}/gmock/ ${CMAKE_BINARY_DIR}/zlib/ ${CMAKE_BINARY_DIR}/sqlite/) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../zlib ) target_link_libraries(UnitTests @@ -136,6 +137,9 @@ else() set (SQLITE3_LIB "/usr/local/lib/libsqlite3.a") elseif(EXISTS "/usr/local/opt/sqlite/lib/libsqlite3.a") set (SQLITE3_LIB "/usr/local/opt/sqlite/lib/libsqlite3.a") + elseif(EXISTS "/opt/homebrew/opt/sqlite/lib/libsqlite3.a") + # Apple Silicon homebrew installs to /opt/homebrew instead of /usr/local + set (SQLITE3_LIB "/opt/homebrew/opt/sqlite/lib/libsqlite3.a") else() set (SQLITE3_LIB "sqlite3") endif() @@ -158,10 +162,9 @@ else() set (PLATFORM_LIBS "atomic") endif() - # Find libraries - message("--- Linking libraries! ") - message("Current Dir: ${CMAKE_CURRENT_SOURCE_DIR}") - message("Binary Dir: ${CMAKE_BINARY_DIR}") + message(STATUS "Linking libraries") + message(STATUS "Current Dir: ${CMAKE_CURRENT_SOURCE_DIR}") + message(STATUS "Binary Dir: ${CMAKE_BINARY_DIR}") include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../lib/ ) @@ -179,8 +182,8 @@ else() ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/googletest/build/lib/ ) - message("GTEST: ${LIBGTEST}") - message("GMOCK: ${LIBGMOCK}") + message(STATUS "GTEST: ${LIBGTEST}") + message(STATUS "GMOCK: ${LIBGMOCK}") target_link_libraries(UnitTests ${LIBGTEST} diff --git a/tests/unittests/HttpClientCurlTests.cpp b/tests/unittests/HttpClientCurlTests.cpp new file mode 100644 index 000000000..889d2ffe7 --- /dev/null +++ b/tests/unittests/HttpClientCurlTests.cpp @@ -0,0 +1,126 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +#include "mat/config.h" + +// These tests only apply to the curl HTTP client path (Linux, non-Apple, non-Android) +#if defined(MATSDK_PAL_CPP11) && !defined(_MSC_VER) && defined(HAVE_MAT_DEFAULT_HTTP_CLIENT) \ + && !defined(__APPLE__) && !defined(ANDROID) + +#include "common/Common.hpp" +#include "http/HttpClient_Curl.hpp" +#include "config/RuntimeConfig_Default.hpp" + +using namespace testing; +using namespace MAT; + +class HttpClientCurlTests : public ::testing::Test +{ +protected: + HttpClient_Curl m_client; +}; + +// --- SetSslVerification wiring --- + +TEST_F(HttpClientCurlTests, SslVerification_DefaultsToTrue) +{ + CurlHttpOperation op("GET", "https://example.com", nullptr); + ASSERT_NE(op.GetHandle(), nullptr); +} + +TEST_F(HttpClientCurlTests, CurlHttpOperation_ConstructsWithVerifyTrue) +{ + CurlHttpOperation op("GET", "https://example.com", nullptr, + std::map(), std::vector(), + false, 5, true, ""); + ASSERT_NE(op.GetHandle(), nullptr); +} + +TEST_F(HttpClientCurlTests, CurlHttpOperation_ConstructsWithVerifyFalse) +{ + CurlHttpOperation op("GET", "https://example.com", nullptr, + std::map(), std::vector(), + false, 5, false, ""); + ASSERT_NE(op.GetHandle(), nullptr); +} + +TEST_F(HttpClientCurlTests, CurlHttpOperation_ConstructsWithCaInfo) +{ + CurlHttpOperation op("GET", "https://example.com", nullptr, + std::map(), std::vector(), + false, 5, true, "/etc/ssl/certs/ca-certificates.crt"); + ASSERT_NE(op.GetHandle(), nullptr); +} + +// --- ILogConfiguration integration --- + +TEST(HttpClientCurlConfigTests, LogConfiguration_SslVerify_DefaultIsTrue) +{ + // defaultRuntimeConfig from RuntimeConfig_Default.hpp has the defaults + bool sslVerify = defaultRuntimeConfig[CFG_MAP_HTTP][CFG_BOOL_HTTP_SSL_VERIFY]; + EXPECT_TRUE(sslVerify); +} + +TEST(HttpClientCurlConfigTests, LogConfiguration_SslCaInfo_DefaultIsEmpty) +{ + const char* caInfo = defaultRuntimeConfig[CFG_MAP_HTTP][CFG_STR_HTTP_SSL_CAINFO]; + EXPECT_STREQ(caInfo, ""); +} + +TEST(HttpClientCurlConfigTests, LogConfiguration_SslVerify_CanBeDisabled) +{ + ILogConfiguration config; + config[CFG_MAP_HTTP][CFG_BOOL_HTTP_SSL_VERIFY] = false; + bool sslVerify = config[CFG_MAP_HTTP][CFG_BOOL_HTTP_SSL_VERIFY]; + EXPECT_FALSE(sslVerify); +} + +TEST(HttpClientCurlConfigTests, LogConfiguration_SslCaInfo_CanBeSet) +{ + ILogConfiguration config; + config[CFG_MAP_HTTP][CFG_STR_HTTP_SSL_CAINFO] = "/custom/ca-bundle.crt"; + const char* caInfo = config[CFG_MAP_HTTP][CFG_STR_HTTP_SSL_CAINFO]; + EXPECT_STREQ(caInfo, "/custom/ca-bundle.crt"); +} + +// --- ApplySettings integration --- + +TEST_F(HttpClientCurlTests, ApplySettings_ReadsSslConfigFromLogConfiguration) +{ + ILogConfiguration config; + config[CFG_MAP_HTTP][CFG_BOOL_HTTP_SSL_VERIFY] = false; + config[CFG_MAP_HTTP][CFG_STR_HTTP_SSL_CAINFO] = "/custom/ca.pem"; + m_client.ApplySettings(config); + // Verify indirectly -- constructing an operation should not fail + SUCCEED(); +} + +TEST_F(HttpClientCurlTests, ApplySettings_DefaultConfigEnablesVerification) +{ + ILogConfiguration config; + m_client.ApplySettings(config); + SUCCEED(); +} + +// --- Thread safety: SetSslVerification concurrent with reads --- + +TEST_F(HttpClientCurlTests, SetSslVerification_ConcurrentCallsNoRace) +{ + // Exercise the atomic + mutex path under contention. + // No assertions on output -- this is a sanitizer/TSAN target. + std::vector> futures; + for (int i = 0; i < 10; ++i) + { + futures.push_back(std::async(std::launch::async, [this, i]() { + m_client.SetSslVerification(i % 2 == 0, (i % 2 == 0) ? "/some/path" : ""); + })); + } + for (auto& f : futures) + { + f.get(); + } + SUCCEED(); +} + +#endif // MATSDK_PAL_CPP11 && !_MSC_VER && HAVE_MAT_DEFAULT_HTTP_CLIENT diff --git a/tests/unittests/HttpClientTests.cpp b/tests/unittests/HttpClientTests.cpp index 99d73248b..4b17bcce5 100644 --- a/tests/unittests/HttpClientTests.cpp +++ b/tests/unittests/HttpClientTests.cpp @@ -53,7 +53,7 @@ class HttpClientTests : public ::testing::Test, { _port = _server.addListeningPort(0); std::ostringstream os; - os << "localhost:" << _port; + os << "127.0.0.1:" << _port; _hostname = os.str(); _server.setServerName(_hostname); _server.addHandler("/simple/", *this); diff --git a/tests/unittests/OfflineStorageTests_Room.cpp b/tests/unittests/OfflineStorageTests_Room.cpp index 5e26154fe..5f5b56af6 100644 --- a/tests/unittests/OfflineStorageTests_Room.cpp +++ b/tests/unittests/OfflineStorageTests_Room.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #ifdef ANDROID #include #endif @@ -527,6 +528,107 @@ TEST_P(OfflineStorageTestsRoom, ReleaseActuallyReleases) { ); } +// Regression test for JNI stale local reference bug in GetAndReserveRecords, +// GetRecords, and ReleaseRecords. Each per-record iteration uses +// pushLocalFrame/popLocalFrame; the jclass obtained on iteration 0 must be a +// global reference to remain valid on iteration 1+. Without the fix, ART's JNI +// checker fires JniAbort (SIGABRT) on the second call to GetObjectClass / +// GetFieldID with the stale local ref. +TEST_P(OfflineStorageTestsRoom, MultiRecordIterationFieldIdValidity) +{ + auto now = PAL::getUtcSystemTimeMs(); + StorageRecordVector input; + // Store 3 records: enough to exercise iterations 0, 1, and 2 of the per-record + // loop, covering both the "first time" (class lookup) and "subsequent" paths. + for (size_t i = 0; i < 3; ++i) { + auto id = "reg-" + std::to_string(i); + input.emplace_back( + id, + id, + EventLatency_Normal, + EventPersistence_Normal, + now, + StorageBlob {static_cast(i + 1), 2, 3}); + } + offlineStorage->StoreRecords(input); + ASSERT_EQ(size_t { 3 }, offlineStorage->GetRecordCount(EventLatency_Normal)); + + // GetAndReserveRecords: all 3 records must be returned with correct field values. + StorageRecordVector found; + EXPECT_TRUE(offlineStorage->GetAndReserveRecords( + [&found](StorageRecord && record) -> bool { + found.push_back(std::move(record)); + return true; + }, 5000)); + ASSERT_EQ(size_t { 3 }, found.size()); + { + // Set-based check: return order is implementation-defined + // (SQLite/Room: insertion order; Memory: LIFO). + std::set blob0_values; + for (auto const& r : found) { + EXPECT_EQ(EventLatency_Normal, r.latency); + EXPECT_EQ(EventPersistence_Normal, r.persistence); + ASSERT_EQ(size_t { 3 }, r.blob.size()); + blob0_values.insert(r.blob[0]); + } + EXPECT_EQ((std::set{1, 2, 3}), blob0_values); + } + + // GetRecords: same 3 records readable via GetRecords (shutdown path). + // Memory's GetRecords delegates to GetAndReserveRecords, so it returns + // nothing when records are already reserved — skip that check for Memory. + if (implementation != StorageImplementation::Memory) { + auto shutdown_found = offlineStorage->GetRecords(true, EventLatency_Unspecified, 0); + ASSERT_EQ(size_t { 3 }, shutdown_found.size()); + std::set blob0_values; + for (auto const& r : shutdown_found) { + EXPECT_EQ(EventLatency_Normal, r.latency); + ASSERT_EQ(size_t { 3 }, r.blob.size()); + blob0_values.insert(r.blob[0]); + } + EXPECT_EQ((std::set{1, 2, 3}), blob0_values); + } + + // Un-reserve without using a retry slot so the retry loop below can start fresh. + { + std::vector initial_ids; + initial_ids.reserve(found.size()); + for (auto const& r : found) { + initial_ids.push_back(r.id); + } + bool fromMemory = false; + offlineStorage->ReleaseRecords(initial_ids, false, HttpHeaders(), fromMemory); + } + + // ReleaseRecords bt_class path: cycle GetAndReserveRecords + ReleaseRecords(true) + // GetMaximumRetryCount()+1 times. On the final cycle the Room impl drops the 3 + // records and returns a non-empty byTenant array, exercising the bt_class loop. + // Without the global-ref fix, iteration 1+ of that loop produces a JNI abort. + auto retries = configMock.GetMaximumRetryCount() + 1; + if (implementation != StorageImplementation::Memory) { + EXPECT_CALL(observerMock, OnStorageRecordsDropped(SizeIs(3))).WillOnce(Return()); + } + for (size_t retry = 0; retry < retries; ++retry) { + found.clear(); + offlineStorage->GetAndReserveRecords( + [&found](StorageRecord && record) -> bool { + found.push_back(std::move(record)); + return true; + }, 5000); + EXPECT_EQ(size_t { 3 }, found.size()) << "retry=" << retry; + std::vector ids; + ids.reserve(found.size()); + for (auto const& r : found) { + ids.push_back(r.id); + } + bool fromMemory = false; + offlineStorage->ReleaseRecords(ids, true, HttpHeaders(), fromMemory); + } + if (implementation != StorageImplementation::Memory) { + EXPECT_EQ(size_t { 0 }, offlineStorage->GetRecordCount(EventLatency_Normal)); + } +} + TEST_P(OfflineStorageTestsRoom, DeleteByToken) { StorageRecordVector records; diff --git a/tests/unittests/PalTests.cpp b/tests/unittests/PalTests.cpp index 1ec078916..15bb98e5e 100644 --- a/tests/unittests/PalTests.cpp +++ b/tests/unittests/PalTests.cpp @@ -85,7 +85,7 @@ TEST_F(PalTests, SystemTime) int64_t t1 = PAL::getUtcSystemTimeMs(); EXPECT_THAT(t1, Gt(t0 + 360)); - EXPECT_THAT(t1, Lt(t0 + 550)); + EXPECT_THAT(t1, Lt(t0 + 1000)); } TEST_F(PalTests, FormatUtcTimestampMsAsISO8601) @@ -103,7 +103,7 @@ TEST_F(PalTests, MonotonicTime) int64_t t1 = PAL::getMonotonicTimeMs(); EXPECT_THAT(t1 - t0, Gt(780)); - EXPECT_THAT(t1 - t0, Lt(950)); + EXPECT_THAT(t1 - t0, Lt(1500)); } TEST_F(PalTests, SemanticContextPopulation) diff --git a/tests/unittests/UnitTests.vcxproj b/tests/unittests/UnitTests.vcxproj index 579c52a83..12f44eae7 100644 --- a/tests/unittests/UnitTests.vcxproj +++ b/tests/unittests/UnitTests.vcxproj @@ -495,7 +495,6 @@ - diff --git a/tests/unittests/obj-c/ODWReachabilityTests.mm b/tests/unittests/obj-c/ODWReachabilityTests.mm index 501a3be7a..cc8a7c24e 100644 --- a/tests/unittests/obj-c/ODWReachabilityTests.mm +++ b/tests/unittests/obj-c/ODWReachabilityTests.mm @@ -13,11 +13,7 @@ #import #import -#import #import -#import -#import -#import @interface ODWReachabilityTests : XCTestCase @end @@ -110,4 +106,3 @@ - (void)testReachabilityForLocalWiFi } @end - diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 0e13591d5..7947f7df0 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -29,11 +29,7 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF #import #import -#import #import -#import -#import -#import NSString *const kNetworkReachabilityChangedNotification = @"NetworkReachabilityChangedNotification"; diff --git a/tools/MakeDeb.cmake b/tools/MakeDeb.cmake index efec5ad32..4f839ab5b 100644 --- a/tools/MakeDeb.cmake +++ b/tools/MakeDeb.cmake @@ -24,7 +24,7 @@ set(CPACK_PACKAGE_VERSION_PATCH "${PATCH_VERSION}") # FIXME: add architecture name in file name set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}-${CPACK_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") -message("-- Package name: ${CPACK_PACKAGE_FILE_NAME}.deb") +message(STATUS "Package name: ${CPACK_PACKAGE_FILE_NAME}.deb") #install(TARGETS ${MAT_SDK_LIB_DIR}/libMAT.a ARCHIVE DESTINATION lib/MAT COMPONENT headers) #install(FILES ${MAT_SDK_INC_DIR}/*.* DESTINATION include/MAT COMPONENT libraries) diff --git a/tools/MakeRpm.cmake b/tools/MakeRpm.cmake index c4db45de0..8d9cf0b67 100644 --- a/tools/MakeRpm.cmake +++ b/tools/MakeRpm.cmake @@ -18,7 +18,7 @@ set(CPACK_PACKAGE_NAME "mat_sdk") set(CPACK_PACKAGE_RELEASE "0") set(CPACK_PACKAGE_VENDOR "Microsoft") set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}.${CMAKE_SYSTEM_PROCESSOR}") -message("-- Package name: ${CPACK_RPM_PACKAGE_FILE_NAME}.rpm") +message(STATUS "Package name: ${CPACK_PACKAGE_FILE_NAME}.rpm") #configure_file("${CMAKE_CURRENT_SOURCE_DIR}/mat-sdk.spec.in" "${CMAKE_CURRENT_BINARY_DIR}/arka-sdk.spec" @ONLY IMMEDIATE) #set(CPACK_RPM_USER_BINARY_SPECFILE "${CMAKE_CURRENT_BINARY_DIR}/mat-sdk.spec") diff --git a/tools/MakeTgz.cmake b/tools/MakeTgz.cmake index bf159ab8d..44c308e32 100644 --- a/tools/MakeTgz.cmake +++ b/tools/MakeTgz.cmake @@ -24,8 +24,7 @@ file(GLOB ALL_TARGET_LIBS "${CMAKE_CURRENT_BINARY_DIR}/lib/libmat.*") install(FILES ${ALL_TARGET_LIBS} DESTINATION lib COMPONENT libraries) #install(FILES ${MAT_SDK_INC_DIR}/*.* DESTINATION include/mat COMPONENT libraries) -# FIXME: add architecture name in file name set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}-${CPACK_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") -message("-- Package name: ${CPACK_PACKAGE_FILE_NAME}.tgz") +message(STATUS "Package name: ${CPACK_PACKAGE_FILE_NAME}.tgz") include(CPack) diff --git a/tools/ParseOsRelease.cmake b/tools/ParseOsRelease.cmake index 48bd0398a..fc3abe9af 100644 --- a/tools/ParseOsRelease.cmake +++ b/tools/ParseOsRelease.cmake @@ -1,6 +1,6 @@ # Parse /etc/os-release to determine Linux distro -if(EXISTS /etc/os-release) +if(EXISTS "/etc/os-release") file(STRINGS /etc/os-release OS_RELEASE) foreach(NameAndValue ${OS_RELEASE}) @@ -13,7 +13,7 @@ foreach(NameAndValue ${OS_RELEASE}) # Strip quotes from value string(REPLACE "\"" "" Value ${Value}) # Set the variable - message("-- /etc/os_release : ${Name}=${Value}") + message(STATUS "/etc/os_release : ${Name}=${Value}") set("OS_RELEASE_${Name}" "${Value}") endforeach() diff --git a/tools/setup-buildtools.cmd b/tools/setup-buildtools.cmd index 5c4705a15..890467256 100644 --- a/tools/setup-buildtools.cmd +++ b/tools/setup-buildtools.cmd @@ -16,8 +16,9 @@ if ERRORLEVEL 0 ( vswhere -property installationPath ) -REM Install tools needed to build SDK with either Visual Studio or CMake -choco install -y cmake svn git llvm zip +REM Install baseline tools needed to build SDK with either Visual Studio or CMake +REM LLVM is optional and handled below when INSTALL_LLVM is defined. +choco install -y cmake svn git zip REM Try to autodetect Visual Studio call "%~dp0\vcvars.cmd"