From d5fe0c042b0dcc1504c95a3e46ae1564d51c59f3 Mon Sep 17 00:00:00 2001 From: Chua <464580613@qq.com> Date: Thu, 10 Sep 2020 18:13:55 +0800 Subject: [PATCH] Add tests for ecs client (#597) * Add tests for ecsclient * Update lib/modules to latest * add github workflows for exp Co-authored-by: Hongji Cai Co-authored-by: Max Golovanov Co-authored-by: Hongji --- .github/workflows/build-posix-latest-exp.yml | 30 ++ .../workflows/build-windows-vs2017-exp.yaml | 37 ++ .github/workflows/test-win-latest-exp.yml | 32 ++ build-tests.cmd | 9 +- build.sh | 25 +- lib/include/mat/config-compact-exp.h | 2 +- lib/modules | 2 +- lib/offline/OfflineStorage_SQLite.cpp | 3 +- tests/functests/CMakeLists.txt | 3 + tests/functests/ECSClientCommon.hpp | 83 +++++ tests/functests/ECSClientFuncTests.cpp | 344 ++++++++++++++++++ .../functests/ECSClientRealworldFuncTests.cpp | 180 +++++++++ tests/functests/ECSConfigCacheFuncTests.cpp | 41 +++ tests/functests/FuncTests.vcxproj | 7 + tests/functests/FuncTests.vcxproj.filters | 4 + tests/unittests/CMakeLists.txt | 3 + tests/unittests/ECSClientTests.cpp | 131 +++++++ tests/unittests/ECSClientUtilsTests.cpp | 30 ++ tests/unittests/ECSConfigCacheTests.cpp | 44 +++ tests/unittests/UnitTests.vcxproj | 6 + tests/unittests/UnitTests.vcxproj.filters | 3 + 21 files changed, 1008 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/build-posix-latest-exp.yml create mode 100644 .github/workflows/build-windows-vs2017-exp.yaml create mode 100644 .github/workflows/test-win-latest-exp.yml create mode 100644 tests/functests/ECSClientCommon.hpp create mode 100644 tests/functests/ECSClientFuncTests.cpp create mode 100644 tests/functests/ECSClientRealworldFuncTests.cpp create mode 100644 tests/functests/ECSConfigCacheFuncTests.cpp create mode 100644 tests/unittests/ECSClientTests.cpp create mode 100644 tests/unittests/ECSClientUtilsTests.cpp create mode 100644 tests/unittests/ECSConfigCacheTests.cpp diff --git a/.github/workflows/build-posix-latest-exp.yml b/.github/workflows/build-posix-latest-exp.yml new file mode 100644 index 00000000..3ad69cfe --- /dev/null +++ b/.github/workflows/build-posix-latest-exp.yml @@ -0,0 +1,30 @@ +name: SDK with exp - C/C++ CI on Linux and Mac + +on: + push: + branches: + - master + - release/* + - buildme/* + pull_request: + branches: + - master + schedule: + - cron: 0 2 * * 1-5 + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + config: [release, debug] + os: [ubuntu-latest, macOS-latest] + + steps: + - uses: actions/checkout@v1 + - name: Test ${{ matrix.os }} ${{ matrix.config }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_PULL_TOKEN: ${{ secrets.GIT_PULL_TOKEN }} + run: ./build.sh ${{ matrix.config }} CUSTOM_BUILD_FLAGS="-DHAVE_MAT_EXP -DHAVE_MAT_FIFOSTORAGE" && ./out/tests/functests/FuncTests && ./out/tests/unittests/UnitTests diff --git a/.github/workflows/build-windows-vs2017-exp.yaml b/.github/workflows/build-windows-vs2017-exp.yaml new file mode 100644 index 00000000..11d1e3eb --- /dev/null +++ b/.github/workflows/build-windows-vs2017-exp.yaml @@ -0,0 +1,37 @@ +name: SDK with exp - C/C++ CI on Windows (vs2017) + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + + runs-on: windows-2016 + name: Build + + steps: + + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Tools + shell: cmd + run: tools\setup-buildtools.cmd + + - name: Build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_PULL_TOKEN: ${{ secrets.GIT_PULL_TOKEN }} + SKIP_ARM_BUILD: 1 + SKIP_ARM64_BUILD: 1 + shell: cmd + run: build-all.bat %CD%\Solutions\build.compact-exp.props + + - name: ListOutput + shell: cmd + run: tree %CD%\Solutions\out diff --git a/.github/workflows/test-win-latest-exp.yml b/.github/workflows/test-win-latest-exp.yml new file mode 100644 index 00000000..80068bf4 --- /dev/null +++ b/.github/workflows/test-win-latest-exp.yml @@ -0,0 +1,32 @@ +name: SDK with exp - Build and Run Unit/Functional tests on Windows + +on: + push: + branches: + - master + - release/* + - buildme/* + pull_request: + branches: + - master + schedule: + - cron: 0 2 * * 1-5 + +jobs: + test: + name: Test on Windows ${{ matrix.arch }}-${{ matrix.config }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + arch: [Win32, x64] + config: [Release, Debug] + os: [windows-2016] + + steps: + - uses: actions/checkout@v1 + - name: Test ${{ matrix.arch }} ${{ matrix.config }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_PULL_TOKEN: ${{ secrets.GIT_PULL_TOKEN }} + shell: cmd + run: build-tests.cmd ${{ matrix.arch }} ${{ matrix.config }} %CD%\Solutions\build.compact-exp.props diff --git a/build-tests.cmd b/build-tests.cmd index ac0e0107..d8f5b22e 100644 --- a/build-tests.cmd +++ b/build-tests.cmd @@ -15,6 +15,13 @@ set PLAT=%1 REM Possible configurations: Release|Debug set CONFIGURATION=%2 +set CUSTOM_PROPS= +if ("%3"=="") goto skip +set CUSTOM_PROPS="/p:ForceImportBeforeCppTargets=%3" +echo Using custom properties file for the build: +echo %CUSTOM_PROPS% +:skip + REM Add path to vs2017 MSBuild.exe set "PATH=%PATH%;C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\" @@ -25,7 +32,7 @@ call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Too set MAXCPUCOUNT=%NUMBER_OF_PROCESSORS% set SOLUTION=Solutions\MSTelemetrySDK.sln -msbuild %SOLUTION% /target:sqlite,zlib,Tests\gmock,Tests\gtest,Tests\UnitTests,Tests\FuncTests /p:BuildProjectReferences=true /maxcpucount:%MAXCPUCOUNT% /detailedsummary /p:Configuration=%CONFIGURATION% /p:Platform=%PLAT% +msbuild %SOLUTION% /target:sqlite,zlib,Tests\gmock,Tests\gtest,Tests\UnitTests,Tests\FuncTests /p:BuildProjectReferences=true /maxcpucount:%MAXCPUCOUNT% /detailedsummary /p:Configuration=%CONFIGURATION% /p:Platform=%PLAT% %CUSTOM_PROPS% if errorLevel 1 goto end Solutions\out\%CONFIGURATION%\%PLAT%\UnitTests\UnitTests.exe if errorLevel 1 goto end diff --git a/build.sh b/build.sh index 7a43ad09..30bb3e84 100755 --- a/build.sh +++ b/build.sh @@ -25,24 +25,36 @@ if [ "$1" == "clean" ]; then # make clean fi -if [ "$1" == "noroot" ] || [ "$2" == "noroot" ]; then +if [ "$1" == "noroot" ] || [ "$2" == "noroot" ] || [ "$3" == "noroot" ]; then export NOROOT=true fi -if [ "$1" == "release" ] || [ "$2" == "release" ]; then +if [ "$1" == "release" ] || [ "$2" == "release" ] || [ "$3" == "release" ]; then BUILD_TYPE="Release" else BUILD_TYPE="Debug" fi -if [ "$1" == "arm64" ] || [ "$2" == "arm64" ]; then +if [ "$1" == "arm64" ] || [ "$2" == "arm64" ] || [ "$3" == "arm64" ]; then MAC_ARCH="arm64" -elif [ "$1" == "universal" ] || [ "$2" == "universal" ]; then +elif [ "$1" == "universal" ] || [ "$2" == "universal" ] || [ "$3" == "universal" ]; then MAC_ARCH="universal" else MAC_ARCH="x86_64" fi +CUSTOM_CMAKE_CXX_FLAG="" +if [[ $1 == CUSTOM_BUILD_FLAGS* ]] || [[ $2 == CUSTOM_BUILD_FLAGS* ]] || [[ $3 == CUSTOM_BUILD_FLAGS* ]]; then + if [[ $1 == CUSTOM_BUILD_FLAGS* ]]; then + CUSTOM_CMAKE_CXX_FLAG="\"${1:19:999}\"" + elif [[ $2 == CUSTOM_BUILD_FLAGS* ]]; then + CUSTOM_CMAKE_CXX_FLAG="\"${2:19:999}\"" + elif [[ $3 == CUSTOM_BUILD_FLAGS* ]]; then + CUSTOM_CMAKE_CXX_FLAG="\"${3:19:999}\"" + fi +fi +echo "custom build flags="$CUSTOM_CMAKE_CXX_FLAG + # Set target MacOS minver export MACOSX_DEPLOYMENT_TARGET=10.10 @@ -92,8 +104,9 @@ fi set -e -# TODO: pass custom build flags? -cmake -DMAC_ARCH=$MAC_ARCH -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PACKAGE_TYPE=$CMAKE_PACKAGE_TYPE .. +cmake_cmd="cmake -DMAC_ARCH=$MAC_ARCH -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PACKAGE_TYPE=$CMAKE_PACKAGE_TYPE -DCMAKE_CXX_FLAGS="${CUSTOM_CMAKE_CXX_FLAG}" .." +echo $cmake_cmd +eval $cmake_cmd # TODO: strip symbols to minimize (release-only) # Build all diff --git a/lib/include/mat/config-compact-exp.h b/lib/include/mat/config-compact-exp.h index e72108f9..d18c87d7 100644 --- a/lib/include/mat/config-compact-exp.h +++ b/lib/include/mat/config-compact-exp.h @@ -7,7 +7,7 @@ #define HAVE_MAT_JSONHPP #define HAVE_MAT_ZLIB /* #define HAVE_MAT_LOGGING */ -/* #define HAVE_MAT_STORAGE */ +#define HAVE_MAT_STORAGE /* #define HAVE_MAT_NETDETECT */ /* #define HAVE_MAT_SHORT_NS */ #define HAVE_MAT_DEFAULT_HTTP_CLIENT diff --git a/lib/modules b/lib/modules index ec8510fd..312407c4 160000 --- a/lib/modules +++ b/lib/modules @@ -1 +1 @@ -Subproject commit ec8510fd1646e5e45779b398b6ab4f0daee8733e +Subproject commit 312407c4f3bff3bf0a7b2d8c8c6fbeaec3fc2131 diff --git a/lib/offline/OfflineStorage_SQLite.cpp b/lib/offline/OfflineStorage_SQLite.cpp index fb6d33cf..5992af07 100644 --- a/lib/offline/OfflineStorage_SQLite.cpp +++ b/lib/offline/OfflineStorage_SQLite.cpp @@ -656,8 +656,7 @@ namespace MAT_NS_BEGIN { std::ostringstream tempPragma; tempPragma << "PRAGMA temp_store_directory = '" << GetTempDirectory() << "'"; SqliteStatement(*m_db, tempPragma.str().c_str()).select(); - const char * result = sqlite3_temp_directory; - LOG_INFO("Set sqlite3 temp_store_directory to '%s'", result); + LOG_INFO("Set sqlite3 temp_store_directory to '%s'", sqlite3_temp_directory); } int openedDbVersion; diff --git a/tests/functests/CMakeLists.txt b/tests/functests/CMakeLists.txt index db7d87e3..51afa95e 100644 --- a/tests/functests/CMakeLists.txt +++ b/tests/functests/CMakeLists.txt @@ -9,6 +9,9 @@ set(SRCS LogSessionDataFuncTests.cpp Main.cpp MultipleLogManagersTests.cpp + ECSClientFuncTests.cpp + ECSClientRealworldFuncTests.cpp + ECSConfigCacheFuncTests.cpp ) source_group(" " REGULAR_EXPRESSION "") diff --git a/tests/functests/ECSClientCommon.hpp b/tests/functests/ECSClientCommon.hpp new file mode 100644 index 00000000..1e6846a3 --- /dev/null +++ b/tests/functests/ECSClientCommon.hpp @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft. All rights reserved. +#pragma once +#include "mat/config.h" +#ifdef HAVE_MAT_EXP +#include +#include +#include +#include +#include +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#endif +#include "common/Common.hpp" +#include "common/HttpServer.hpp" +#include "utils/Utils.hpp" +#include "modules/exp/ecs/ecsclient/ECSClient.hpp" +#include "json.hpp" +#include "utils/StringUtils.hpp" +using json = nlohmann::json; + +using namespace testing; +using namespace MAT; +using namespace Microsoft::Applications::Experimentation::ECS; +using namespace Microsoft::Applications::Events; + +namespace ECSClientCommon{ + const int maxRetryCount = 50; + class ECSClientCallback : public IECSClientCallback + { + std::function callback; + public: + ECSClientCallback(const std::function &callback):callback(callback){ } + + virtual void OnECSClientEvent(ECSClientEventType evtType, ECSClientEventContext evtContext) + { + if (evtType == ECSClientEventType::ET_CONFIG_UPDATE_SUCCEEDED) + { + callback(); + } + } + }; + + inline std::unique_ptr GetInitilizedECSClient(const ECSClientConfiguration& config, const std::function &callback = nullptr) + { + auto client = std::make_unique(); + if (callback) + { + callback(client.get()); + } + client->Initialize(config); + return client; + } + + inline void InitilizeAndStartECSClientThen(const ECSClientConfiguration& config, const std::function &callback, const std::function &initCallback = nullptr) + { + auto client = GetInitilizedECSClient(config); + if (initCallback) + { + initCallback(client.get()); + } + auto ret = client->Start(); + ASSERT_EQ(ret, true); + bool configUpdated = false; + auto onConfigUpdate = std::make_unique([&configUpdated](){ + configUpdated = true; + }); + client->AddListener(onConfigUpdate.get()); + int retryCount = 0; + while(!configUpdated && retryCount < maxRetryCount){ + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + ++retryCount; + }; + if(configUpdated) + { + callback(client.get()); + } + else + { + FAIL() << "Config should be updated"; + } + } +} +#endif \ No newline at end of file diff --git a/tests/functests/ECSClientFuncTests.cpp b/tests/functests/ECSClientFuncTests.cpp new file mode 100644 index 00000000..1d66a582 --- /dev/null +++ b/tests/functests/ECSClientFuncTests.cpp @@ -0,0 +1,344 @@ +// Copyright (c) Microsoft. All rights reserved. +#include "ECSClientCommon.hpp" + +#ifdef HAVE_MAT_EXP + +namespace { + using namespace ECSClientCommon; + int port = 8888; + const char* const jsonConfigString = "{\"ECSDemo\":{\"intArray\":[1,2],\"dblArray\":[1.1],\"strArray\":[\"hello\"],\"bool\":true,\"int\":123,\"double\":1.1,\"string\":\"str\",\"object\":{\"a\":\"b\",\"c\":\"d\"},\"null\":null},\"Headers\":{\"ETag\":\"\\\"kq49sHJi58LmvklHzjiuJ4UNE/VdiaeFjrYkErAZr1w=\\\"\",\"Expires\":\"Fri, 14 Aug 2020 07:12:13 GMT\",\"CountryCode\":\"JP\",\"StatusCode\":\"200\"}}"; + const char* const agent = "ECSDemo"; + const char* const userIdHitString = "{\"hit\":\"userId\"}"; + const char* const deviceIdHitString = "{\"hit\":\"clientId\"}"; + const char* const filterHitString = "{\"hit\":\"filter\"}"; + const char* const etagVal = "\"kq49sHJi58LmvklHzjiuJ4UNE/VdiaeFjrYkErAZr1w=\""; + const char* const cacheFilePathName = "cacheFilePathName"; + class ECSServerCallback : public HttpServer::Callback + { + public: + virtual int onHttpRequest(HttpServer::Request const& request, HttpServer::Response& response) override + { + std::vector uriParam; + std::vector params; + StringUtils::SplitString(request.uri, '?', uriParam); + if (uriParam.size() == 2) + { + StringUtils::SplitString(uriParam[1], '&', params); + for (auto param : params) + { + std::vector kv; + StringUtils::SplitString(param, '=', kv); + if (kv.size() == 2) + { + if (kv[0] == "id" && kv[1] == "hocai") + { + response.content = userIdHitString; + return 200; + } + + if (kv[0] == "clientId" && kv[1] == "hocaiDevice") + { + response.content = deviceIdHitString; + return 200; + } + + if (kv[0] == "customFilter" && kv[1] == "filterVal") + { + response.content = filterHitString; + return 200; + } + + if (kv[0] == "customStatusCode") + { + response.content = jsonConfigString; + return std::stoi(kv[1]); + } + } + } + } + + response.headers = std::map + { + {"etag", etagVal}, + {"date", "Thu, 20 Aug 2020 01:28:19 GMT"}, + {"expires", "Thu, 20 Aug 2020 01:28:19 GMT"} + }; + response.content = jsonConfigString; + return 200; + } + }; + + class ECSClientFuncTests : public ::testing::Test + { + protected: + HttpServer server; + ECSServerCallback callback; + const std::string offlineStoragePath = ECSConfigCache::GetStoragePath(cacheFilePathName); + + virtual void SetUp() + { + // there are some error on windows + // looks like port didn't get recycled immediately so we randomize port + port += 1; + std:: cout<< "server port:" << port << std::endl; + server.addListeningPort(port); + server.addHandler("/config/v1", callback); + server.start(); + std::remove(offlineStoragePath.c_str()); + } + + virtual void TearDown() + { + server.stop(); + std::remove(offlineStoragePath.c_str()); + } + }; + + ECSClientConfiguration GetECSClientConfiguration() + { + auto config = ECSClientConfiguration(); + config.clientName = "Test"; + config.clientVersion = "1.0"; + config.cacheFilePathName = cacheFilePathName; + config.serverUrls.push_back("http://127.0.0.1:" + std::to_string(port) + "/config/v1"); + return config; + } + + TEST_F(ECSClientFuncTests, GetConfigs) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto configs = client->GetConfigs(); + ASSERT_EQ(json::parse(configs), json::parse(jsonConfigString)); + }); + } + + TEST_F(ECSClientFuncTests, GetETag) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto etag = client->GetETag(); + ASSERT_EQ(etag, etagVal); + }); + } + + TEST_F(ECSClientFuncTests, GetSetting_Int) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetSetting(agent, "int", 0); + ASSERT_EQ(ret, 123); + }); + } + + TEST_F(ECSClientFuncTests, GetSetting_Bool) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetSetting(agent, "bool", false); + ASSERT_EQ(ret, true); + }); + } + + TEST_F(ECSClientFuncTests, GetSetting_Double) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetSetting(agent, "double", 0.0); + EXPECT_DOUBLE_EQ(ret, 1.1); + }); + } + + TEST_F(ECSClientFuncTests, GetSetting_String) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetSetting(agent, "string", std::string()); + ASSERT_EQ(ret, std::string("str")); + }); + } + + TEST_F(ECSClientFuncTests, GetSettingsAsInts) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetSettingsAsInts(agent, "intArray"); + ASSERT_THAT(ret, ElementsAre(1, 2)); + }); + } + + TEST_F(ECSClientFuncTests, GetSettings) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetSettings(agent, "strArray"); + ASSERT_THAT(ret, ElementsAre(std::string("hello"))); + }); + } + + TEST_F(ECSClientFuncTests, GetSettingsAsDbls) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetSettingsAsDbls(agent, "dblArray"); + ASSERT_THAT(ret, ElementsAre(1.1)); + }); + } + + TEST_F(ECSClientFuncTests, TryGetSetting_String) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + std::string val; + auto ret = client->TryGetSetting(agent, "string", val); + ASSERT_EQ(ret, true); + ASSERT_EQ(val, "str"); + }); + } + + TEST_F(ECSClientFuncTests, TryGetSetting_Long) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + long val = 0; + auto ret = client->TryGetLongSetting(agent, "int", val); + ASSERT_EQ(ret, true); + ASSERT_EQ(val, 123); + }); + } + + TEST_F(ECSClientFuncTests, TryGetSetting_Bool) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + bool val = 0; + auto ret = client->TryGetBoolSetting(agent, "bool", val); + ASSERT_EQ(ret, true); + ASSERT_EQ(val, true); + }); + } + + TEST_F(ECSClientFuncTests, TryGetSetting_Int) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + int val = 0; + auto ret = client->TryGetIntSetting(agent, "int", val); + ASSERT_EQ(ret, true); + ASSERT_EQ(val, 123); + }); + } + + TEST_F(ECSClientFuncTests, TryGetSetting_Double) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + double val = 0.0; + auto ret = client->TryGetDoubleSetting(agent, "double", val); + ASSERT_EQ(ret, true); + + EXPECT_DOUBLE_EQ(val, 1.1); + }); + } + + TEST_F(ECSClientFuncTests, GetKeys) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetKeys(agent, ""); + ASSERT_THAT(ret, ElementsAre("bool", "dblArray", "double", "int", "intArray", "null", "object", "strArray", "string")); + }); + } + + TEST_F(ECSClientFuncTests, SetUserId) + { + auto initCallback = [](ECSClient* client){ + client->SetUserId("hocai"); + }; + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetConfigs(); + ASSERT_EQ(ret, userIdHitString); + }, initCallback); + } + + TEST_F(ECSClientFuncTests, SetDeviceId) + { + auto initCallback = [](ECSClient* client){ + client->SetDeviceId("hocaiDevice"); + }; + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetConfigs(); + ASSERT_EQ(ret, deviceIdHitString); + }, initCallback); + } + + TEST_F(ECSClientFuncTests, SetRequestParameters) + { + auto initCallback = [](ECSClient* client){ + client->SetRequestParameters(std::map{{"customFilter","filterVal"}}); + }; + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetConfigs(); + ASSERT_EQ(ret, filterHitString); + }, initCallback); + } + + TEST_F(ECSClientFuncTests, GetActiveConfigVariant_NotStarted) + { + auto client = GetInitilizedECSClient(GetECSClientConfiguration()); + auto configVariant = client->GetActiveConfigVariant(); + ASSERT_EQ(configVariant, json()); + } + + TEST_F(ECSClientFuncTests, GetActiveConfigVariant_Started) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto configVariant = client->GetActiveConfigVariant(); + ASSERT_EQ(configVariant, json::parse(jsonConfigString)); + }); + } + + TEST_F(ECSClientFuncTests, GetExpiryTimeInSec_NotStarted) + { + auto client = GetInitilizedECSClient(GetECSClientConfiguration()); + auto expiryTimeInSec = client->GetExpiryTimeInSec(); + ASSERT_EQ(expiryTimeInSec, DEFAULT_EXPIRE_INTERVAL_IN_SECONDS_MIN); + } + + TEST_F(ECSClientFuncTests, GetExpiryTimeInSec_Started) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto expiryTimeInSec = client->GetExpiryTimeInSec(); + // server return expriy time is zero, client will use DEFAULT_EXPIRE_INTERVAL_IN_SECONDS_MIN as expiry time + ASSERT_EQ(expiryTimeInSec > 0, true); + ASSERT_LE(expiryTimeInSec, DEFAULT_EXPIRE_INTERVAL_IN_SECONDS_MIN); + }); + } +} + +#endif \ No newline at end of file diff --git a/tests/functests/ECSClientRealworldFuncTests.cpp b/tests/functests/ECSClientRealworldFuncTests.cpp new file mode 100644 index 00000000..5c788c87 --- /dev/null +++ b/tests/functests/ECSClientRealworldFuncTests.cpp @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft. All rights reserved. +#include "ECSClientCommon.hpp" +#ifdef HAVE_MAT_EXP + +namespace { + // config: https://ecs.skype.net/?page=ExperimentPage&type=Rollout&id=45994 + + using namespace ECSClientCommon; + const char* const clientName = "ECS"; + const char* const agent = "ECSDemo"; + const char* const cacheFilePathName = "cacheFilePathName"; + + class ECSClientRealworldFuncTests : public ::testing::Test + { + protected: + std::string offlineStoragePath = ECSConfigCache::GetStoragePath(cacheFilePathName); + + virtual void SetUp() + { + std::remove(offlineStoragePath.c_str()); + } + + virtual void TearDown() + { + std::remove(offlineStoragePath.c_str()); + } + }; + + ECSClientConfiguration GetECSClientConfiguration() + { + auto config = ECSClientConfiguration(); + config.clientName = clientName; + config.clientVersion = "1"; + config.cacheFilePathName = cacheFilePathName; + config.serverUrls.push_back("https://s2s.config.skype.net/config/v1/"); + return config; + } + + TEST_F(ECSClientRealworldFuncTests, GetConfigs) + { + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + { + auto configs = client->GetConfigs(); + std::cout<< configs << std::endl; + ASSERT_EQ(json::parse(configs)["ECSDemo"]["hit"], std::string("default")); + } + + // TEST_F(ECSClientRealworldFuncTests, GetETag) + { + auto etag = client->GetETag(); + std::cout<< etag << std::endl; + // etag start with " and end with " + // no need to check is it equal because config change will modify the val + ASSERT_EQ(0, etag.find("\"")); + ASSERT_EQ(etag.size()-1, etag.rfind("\"")); + } + + // TEST_F(ECSClientRealworldFuncTests, GetSetting_Int) + { + auto ret = client->GetSetting(agent, "int", 0); + ASSERT_EQ(ret, 123); + } + + // TEST_F(ECSClientRealworldFuncTests, GetSetting_Bool) + { + auto ret = client->GetSetting(agent, "bool", false); + ASSERT_EQ(ret, true); + } + + // TEST_F(ECSClientRealworldFuncTests, GetSetting_Double) + { + auto ret = client->GetSetting(agent, "double", (double)0); + EXPECT_DOUBLE_EQ(ret, 1.1); + } + + // TEST_F(ECSClientRealworldFuncTests, GetSetting_String) + { + auto ret = client->GetSetting(agent, "string", std::string()); + ASSERT_EQ(ret, std::string("str")); + } + + // TEST_F(ECSClientRealworldFuncTests, GetSettingsAsInts) + { + auto ret = client->GetSettingsAsInts(agent, "intArray"); + ASSERT_THAT(ret, ElementsAre(1, 2)); + } + + // TEST_F(ECSClientRealworldFuncTests, GetSettings) + { + auto ret = client->GetSettings(agent, "strArray"); + ASSERT_THAT(ret, ElementsAre(std::string("hello"))); + } + + // TEST_F(ECSClientRealworldFuncTests, GetSettingsAsDbls) + { + auto ret = client->GetSettingsAsDbls(agent, "dblArray"); + ASSERT_THAT(ret, ElementsAre(1.1)); + } + + // TEST_F(ECSClientRealworldFuncTests, TryGetSetting_String) + { + std::string val = ""; + auto ret = client->TryGetSetting(agent, "string", val); + ASSERT_EQ(ret, true); + ASSERT_EQ(val, "str"); + } + + // TEST_F(ECSClientRealworldFuncTests, TryGetSetting_Long) + { + long val = 0; + auto ret = client->TryGetLongSetting(agent, "int", val); + ASSERT_EQ(ret, true); + ASSERT_EQ(val, 123); + } + + // TEST_F(ECSClientRealworldFuncTests, TryGetSetting_Bool) + { + bool val = 0; + auto ret = client->TryGetBoolSetting(agent, "bool", val); + ASSERT_EQ(ret, true); + ASSERT_EQ(val, true); + } + + // TEST_F(ECSClientRealworldFuncTests, TryGetSetting_Int) + { + int val = 0; + auto ret = client->TryGetIntSetting(agent, "int", val); + ASSERT_EQ(ret, true); + ASSERT_EQ(val, 123); + } + + // TEST_F(ECSClientRealworldFuncTests, TryGetSetting_Double) + { + double val = 0; + auto ret = client->TryGetDoubleSetting(agent, "double", val); + ASSERT_EQ(ret, true); + EXPECT_DOUBLE_EQ(val, 1.1); + } + + // TEST_F(ECSClientRealworldFuncTests, GetKeys) + { + auto ret = client->GetKeys(agent, ""); + for (auto& key: std::vector{"bool", "dblArray", "double", "int", "intArray", "null", "object", "strArray", "string", "hit"}) + { + ASSERT_NE(std::find(ret.begin(), ret.end(), key), ret.end()); + } + } + }); + } + + TEST_F(ECSClientRealworldFuncTests, SetUserId) + { + auto initCallback = [](ECSClient* client){ + client->SetUserId("testuserid"); + }; + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetSetting(agent, "hit", std::string()); + ASSERT_EQ(ret, std::string("userId")); + }, initCallback); + } + + TEST_F(ECSClientRealworldFuncTests, SetRequestParameters) + { + auto initCallback = [](ECSClient* client){ + client->SetRequestParameters(std::map{{"projectTeam","1DS"}}); + }; + InitilizeAndStartECSClientThen( + GetECSClientConfiguration(), + [](ECSClient* client){ + auto ret = client->GetSetting(agent, "hit", std::string()); + ASSERT_EQ(ret, std::string("filter")); + }, initCallback); + } +} + +#endif \ No newline at end of file diff --git a/tests/functests/ECSConfigCacheFuncTests.cpp b/tests/functests/ECSConfigCacheFuncTests.cpp new file mode 100644 index 00000000..ab5ebe8d --- /dev/null +++ b/tests/functests/ECSConfigCacheFuncTests.cpp @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. + +#include "mat/config.h" +#ifdef HAVE_MAT_EXP +#include "common/Common.hpp" +#include "utils/Utils.hpp" +#include "modules/exp/ecs/ecsclient/ECSConfigCache.hpp" + +using namespace testing; +using namespace MAT; +using namespace Microsoft::Applications::Experimentation::ECS; + +TEST(ECSConfigCacheFuncTests, SaveConfig) +{ + const std::string storagePath = "storagePath"; + const std::string requestName = "requestName"; + auto configCache = std::make_unique(storagePath); + ECSConfig config; + config.requestName = requestName; + config.etag = "etag"; + auto addedConfig = configCache->AddConfig(config); + ASSERT_EQ(addedConfig->etag, configCache->GetConfigByRequestName(requestName)->etag); + ASSERT_EQ(true, configCache->SaveConfig(config)); +} + +TEST(ECSConfigCacheFuncTests, LoadConfig) +{ + const std::string storagePath = "storagePath"; + const std::string requestName = "requestName"; + auto configCache = std::make_unique(storagePath); + ECSConfig config; + config.requestName = requestName; + config.etag = "etag"; + auto addedConfig = configCache->AddConfig(config); + ASSERT_EQ(addedConfig->etag, configCache->GetConfigByRequestName(requestName)->etag); + ASSERT_EQ(true, configCache->SaveConfig(config)); + + configCache->LoadConfig(); +} + +#endif \ No newline at end of file diff --git a/tests/functests/FuncTests.vcxproj b/tests/functests/FuncTests.vcxproj index de656c4d..2e4eb94b 100644 --- a/tests/functests/FuncTests.vcxproj +++ b/tests/functests/FuncTests.vcxproj @@ -38,6 +38,9 @@ {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + {216a8e97-21f7-4bef-9e52-7f772c177c32} @@ -423,6 +426,10 @@ + + + + diff --git a/tests/functests/FuncTests.vcxproj.filters b/tests/functests/FuncTests.vcxproj.filters index eedf3eab..a5dadb26 100644 --- a/tests/functests/FuncTests.vcxproj.filters +++ b/tests/functests/FuncTests.vcxproj.filters @@ -14,6 +14,10 @@ + + + + diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 7ecbb337..0372fc8b 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -42,6 +42,9 @@ set(SRCS TransmitProfileRuleTests.cpp TransmitProfilesTests.cpp UtilsTests.cpp + ECSClientTests.cpp + ECSClientUtilsTests.cpp + ECSConfigCacheTests.cpp ) if (APPLE) diff --git a/tests/unittests/ECSClientTests.cpp b/tests/unittests/ECSClientTests.cpp new file mode 100644 index 00000000..5562644c --- /dev/null +++ b/tests/unittests/ECSClientTests.cpp @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft. All rights reserved. +#include "mat/config.h" +#ifdef HAVE_MAT_EXP +#include +#include "common/Common.hpp" +#include "utils/Utils.hpp" +#include "modules/exp/ecs/ecsclient/ECSClient.hpp" + +using namespace testing; +using namespace MAT; +using namespace Microsoft::Applications::Experimentation::ECS; + +std::unique_ptr GetInitializedECSClient() +{ + auto client = std::make_unique(); + auto config = ECSClientConfiguration(); + config.clientName = "Test"; + config.clientVersion = "1.0"; + config.cacheFilePathName = "cacheFilePathName"; + + client->Initialize(config); + return client; +} + +class ECSClientCallback : public IECSClientCallback +{ + std::function callback; +public: + ECSClientCallback(std::function callback = nullptr):callback(callback){ } + + virtual void OnECSClientEvent(ECSClientEventType evtType, ECSClientEventContext evtContext) + { + if (evtType == ECSClientEventType::ET_CONFIG_UPDATE_SUCCEEDED) + { + callback(); + } + } +}; + +TEST(ECSClientTests, CreateInstance_OK_DestroyInstance_OK) +{ + auto client = IECSClient::CreateInstance(); + IECSClient::DestroyInstance(&client); +} + +TEST(ECSClientTests, DestroyInstance_ArgsIsNullptr) +{ + auto client = IECSClient::CreateInstance(); + IECSClient::DestroyInstance(&client); + IECSClient::DestroyInstance(&client); +} + +TEST(ECSClientTests, Start_Failed_NotInitialized) +{ + auto client = std::make_unique(); + auto ret = client->Start(); + ASSERT_EQ(ret, false); +} + +TEST(ECSClientTests, Start_Failed_AlreadyStarted) +{ + auto client = std::make_shared(); + auto config = ECSClientConfiguration(); + config.clientName = "Test"; + config.clientVersion = "1.0"; + config.cacheFilePathName = "cacheFilePathName"; + + client->Initialize(config); + client->Start(); + auto ret = client->Start(); + ASSERT_EQ(ret, false); +} + +TEST(ECSClientTests, Start_OK) +{ + auto client = GetInitializedECSClient(); + auto ret = client->Start(); + ASSERT_EQ(ret, true); +} + +TEST(ECSClientTests, Stop_OK) +{ + auto client = GetInitializedECSClient(); + auto ret = client->Start(); + ASSERT_EQ(ret, true); + ret = client->Stop(); + ASSERT_EQ(ret, true); +} + +TEST(ECSClientTests, Suspend_OK) +{ + auto client = GetInitializedECSClient(); + auto ret = client->Start(); + ASSERT_EQ(ret, true); + ret = client->Suspend(); + ASSERT_EQ(ret, true); +} + +TEST(ECSClientTests, Resume_OK) +{ + auto client = GetInitializedECSClient(); + auto ret = client->Start(); + ASSERT_EQ(ret, true); + ret = client->Suspend(); + ASSERT_EQ(ret, true); + ret = client->Resume(); + ASSERT_EQ(ret, true); +} + +TEST(ECSClientTests, AddListener) +{ + auto client = GetInitializedECSClient(); + auto callback = std::make_unique(); + auto ret = client->AddListener(callback.get()); + ASSERT_EQ(ret, true); + ret = client->AddListener(callback.get()); + ASSERT_EQ(ret, false); +} + +TEST(ECSClientTests, RemoveListener) +{ + auto client = GetInitializedECSClient(); + auto callback = std::make_unique(); + auto ret = client->RemoveListener(callback.get()); + ASSERT_EQ(ret, false); + client->AddListener(callback.get()); + ret = client->RemoveListener(callback.get()); + ASSERT_EQ(ret, true); +} + +#endif \ No newline at end of file diff --git a/tests/unittests/ECSClientUtilsTests.cpp b/tests/unittests/ECSClientUtilsTests.cpp new file mode 100644 index 00000000..663009ad --- /dev/null +++ b/tests/unittests/ECSClientUtilsTests.cpp @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All rights reserved. + +#include "mat/config.h" +#ifdef HAVE_MAT_EXP +#include "common/Common.hpp" +#include "utils/Utils.hpp" +#include "modules/exp/ecs/ecsclient/ECSClientUtils.hpp" + +using namespace testing; +using namespace MAT; +using namespace Microsoft::Applications::Experimentation::ECS; + +TEST(ECSClientUtilsTests, CreateServerUrl) +{ + const std::string serverUrl = "https://this.is.a.fake.endpoint.com/config/v1"; + const std::string clientName = "ATM"; + const std::string clientVersion = "1.0.0.0"; + const auto ret = CreateServerUrl(serverUrl, clientName, clientVersion); + ASSERT_EQ(ret, "https://this.is.a.fake.endpoint.com/config/v1/ATM/1.0.0.0"); +} + +TEST(ECSClientUtilsTests, CreateServerUrl_EmptyClientVersion) +{ + const std::string serverUrl = "https://this.is.a.fake.endpoint.com/config/v1"; + const std::string clientName = "ATM"; + const std::string clientVersion = ""; + const auto ret = CreateServerUrl(serverUrl, clientName, clientVersion); + ASSERT_EQ(ret, "https://this.is.a.fake.endpoint.com/config/v1/ATM"); +} +#endif \ No newline at end of file diff --git a/tests/unittests/ECSConfigCacheTests.cpp b/tests/unittests/ECSConfigCacheTests.cpp new file mode 100644 index 00000000..9db25aab --- /dev/null +++ b/tests/unittests/ECSConfigCacheTests.cpp @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +#include "mat/config.h" +#ifdef HAVE_MAT_EXP +#include "common/Common.hpp" +#include "utils/Utils.hpp" +#include "modules/exp/ecs/ecsclient/ECSConfigCache.hpp" + +using namespace testing; +using namespace MAT; +using namespace Microsoft::Applications::Experimentation::ECS; + +TEST(ECSConfigCacheTests, Constrcut_OK_RelativePath) +{ + const std::string storagePath = "path"; + std::make_unique(storagePath); +} + +TEST(ECSConfigCacheTests, Constrcut_OK_AbsolutePath) +{ + const std::string storagePath = std::string("parentfolder") + PATH_SEPARATOR_CHAR + "childfolder"; + std::make_unique(storagePath); +} + +TEST(ECSConfigCacheTests, AddConfig) +{ + const std::string storagePath = "path"; + auto configCache = std::make_unique(storagePath); + const std::string requestName = "requestName"; + ECSConfig config; + config.requestName = requestName; + config.etag = "etag"; + auto addedConfig = configCache->AddConfig(config); + ASSERT_EQ(addedConfig->etag, configCache->GetConfigByRequestName(requestName)->etag); +} + +TEST(ECSConfigCacheTests, GetConfigByRequestName_NotExistConfig) +{ + const std::string storagePath = "path"; + auto configCache = std::make_unique(storagePath); + ASSERT_EQ(NULL, configCache->GetConfigByRequestName("requestName")); +} + +#endif \ No newline at end of file diff --git a/tests/unittests/UnitTests.vcxproj b/tests/unittests/UnitTests.vcxproj index e112c577..e892eea8 100644 --- a/tests/unittests/UnitTests.vcxproj +++ b/tests/unittests/UnitTests.vcxproj @@ -33,6 +33,9 @@ {c8f6c172-56f2-4e76-b5fa-c3b423b31be7} + + {216a8e97-21f7-4bef-9e52-7f772c177c32} + {2ebc7b3c-2af1-442c-9285-cab39bbb8c00} @@ -460,6 +463,9 @@ + + + diff --git a/tests/unittests/UnitTests.vcxproj.filters b/tests/unittests/UnitTests.vcxproj.filters index 0079c91f..31488ca3 100644 --- a/tests/unittests/UnitTests.vcxproj.filters +++ b/tests/unittests/UnitTests.vcxproj.filters @@ -51,6 +51,9 @@ + + +